├── .gitignore ├── .rvmrc ├── Gemfile ├── Gemfile.lock ├── MIT-LICENSE ├── README.md ├── Rakefile ├── VERSION ├── central_logger.gemspec ├── lib ├── central_logger.rb └── central_logger │ ├── filter.rb │ ├── initializer.rb │ ├── initializer_mixin.rb │ ├── mongo_logger.rb │ ├── railtie.rb │ └── replica_set_helper.rb └── test ├── active_record.rb ├── config └── samples │ ├── central_logger.yml │ ├── database.yml │ ├── database_no_file_logging.yml │ ├── database_replica_set.yml │ ├── database_with_auth.yml │ └── mongoid.yml ├── rails.rb ├── rails ├── 2 │ ├── Gemfile │ ├── Gemfile.lock │ ├── README │ ├── Rakefile │ ├── app │ │ ├── controllers │ │ │ ├── application_controller.rb │ │ │ └── order_controller.rb │ │ ├── helpers │ │ │ └── application_helper.rb │ │ └── views │ ├── config │ │ ├── boot.rb │ │ ├── database.yml │ │ ├── environment.rb │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── production.rb │ │ │ └── test.rb │ │ ├── initializers │ │ │ ├── backtrace_silencers.rb │ │ │ ├── cookie_verification_secret.rb │ │ │ ├── inflections.rb │ │ │ ├── mime_types.rb │ │ │ ├── new_rails_defaults.rb │ │ │ └── session_store.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── preinitializer.rb │ │ └── routes.rb │ ├── db │ │ ├── schema.rb │ │ └── seeds.rb │ ├── doc │ │ └── README_FOR_APP │ ├── public │ │ ├── 404.html │ │ ├── 422.html │ │ ├── 500.html │ │ ├── favicon.ico │ │ ├── images │ │ │ └── rails.png │ │ ├── index.html │ │ ├── javascripts │ │ │ ├── application.js │ │ │ ├── controls.js │ │ │ ├── dragdrop.js │ │ │ ├── effects.js │ │ │ └── prototype.js │ │ └── robots.txt │ ├── script │ │ ├── about │ │ ├── console │ │ ├── dbconsole │ │ ├── destroy │ │ ├── generate │ │ ├── performance │ │ │ ├── benchmarker │ │ │ └── profiler │ │ ├── plugin │ │ ├── runner │ │ └── server │ └── test ├── 3 │ ├── Gemfile │ ├── Gemfile.lock │ ├── README │ ├── Rakefile │ ├── app │ │ ├── controllers │ │ │ ├── application_controller.rb │ │ │ └── order_controller.rb │ │ ├── helpers │ │ │ ├── application_helper.rb │ │ │ └── order_helper.rb │ │ └── views │ ├── config.ru │ ├── config │ │ ├── application.rb │ │ ├── boot.rb │ │ ├── database.yml │ │ ├── environment.rb │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── production.rb │ │ │ └── test.rb │ │ ├── initializers │ │ │ ├── backtrace_silencers.rb │ │ │ ├── inflections.rb │ │ │ ├── mime_types.rb │ │ │ ├── secret_token.rb │ │ │ └── session_store.rb │ │ ├── locales │ │ │ └── en.yml │ │ └── routes.rb │ ├── db │ │ ├── schema.rb │ │ └── seeds.rb │ ├── doc │ │ └── README_FOR_APP │ ├── lib │ │ └── tasks │ │ │ └── .gitkeep │ ├── public │ │ ├── 404.html │ │ ├── 422.html │ │ ├── 500.html │ │ ├── favicon.ico │ │ ├── images │ │ │ └── rails.png │ │ ├── index.html │ │ ├── javascripts │ │ │ ├── application.js │ │ │ ├── controls.js │ │ │ ├── dragdrop.js │ │ │ ├── effects.js │ │ │ ├── prototype.js │ │ │ └── rails.js │ │ ├── robots.txt │ │ └── stylesheets │ │ │ └── .gitkeep │ ├── script │ │ └── rails │ ├── test │ └── vendor │ │ └── plugins │ │ └── .gitkeep └── common │ ├── app │ ├── controllers │ │ └── order_controller.rb │ └── views │ │ ├── layouts │ │ └── application.html.erb │ │ └── order │ │ ├── create.html.erb │ │ └── show.html.erb │ └── test │ ├── functional │ └── order_controller_test.rb │ └── test_helper.rb ├── shoulda_macros └── log_macros.rb ├── test.sh ├── test_helper.rb └── unit ├── central_logger_replica_test.rb └── central_logger_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## PROJECT::GENERAL 17 | coverage 18 | rdoc 19 | pkg 20 | .bundle 21 | cscope.files 22 | cscope.out 23 | tags 24 | 25 | ## PROJECT::SPECIFIC 26 | *.log 27 | *_db 28 | -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm 1.9.2@central_logger 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem "rake" 4 | gem "bundler" 5 | gem "mongo" 6 | gem "bson_ext" 7 | 8 | group :development do 9 | # adds Bundler support for gemspec generation 10 | gem "jeweler", "~> 1.5.0.pre5" 11 | gem "shoulda" 12 | gem "i18n" 13 | gem "activesupport" 14 | gem "mocha" 15 | gem (RUBY_VERSION =~ /^1\.9/ ? "ruby-debug19" : "ruby-debug") 16 | end 17 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | activesupport (3.0.5) 5 | archive-tar-minitar (0.5.2) 6 | bson (1.2.4) 7 | bson_ext (1.2.4) 8 | columnize (0.3.2) 9 | git (1.2.5) 10 | i18n (0.5.0) 11 | jeweler (1.5.2) 12 | bundler (~> 1.0.0) 13 | git (>= 1.2.5) 14 | rake 15 | linecache19 (0.5.11) 16 | ruby_core_source (>= 0.1.4) 17 | mocha (0.9.12) 18 | mongo (1.2.4) 19 | bson (>= 1.2.4) 20 | rake (0.8.7) 21 | ruby-debug-base19 (0.11.24) 22 | columnize (>= 0.3.1) 23 | linecache19 (>= 0.5.11) 24 | ruby_core_source (>= 0.1.4) 25 | ruby-debug19 (0.11.6) 26 | columnize (>= 0.3.1) 27 | linecache19 (>= 0.5.11) 28 | ruby-debug-base19 (>= 0.11.19) 29 | ruby_core_source (0.1.4) 30 | archive-tar-minitar (>= 0.5.2) 31 | shoulda (2.11.3) 32 | 33 | PLATFORMS 34 | ruby 35 | 36 | DEPENDENCIES 37 | activesupport 38 | bson_ext 39 | bundler 40 | i18n 41 | jeweler (~> 1.5.0.pre5) 42 | mocha 43 | mongo 44 | rake 45 | ruby-debug19 46 | shoulda 47 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 [name of plugin creator] 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CentralLogger 2 | 3 | Log to a central MongoDB from Rails apps. 4 | 5 | ## Usage 6 | 7 | 1. If using Bundler, add the following to your Gemfile then refresh your dependencies by executing "bundle install": 8 | 9 | gem "central_logger" 10 | 11 | 1. If you're just using gem: 12 | 13 | gem install central_logger 14 | 15 | 1. Add the following line to your ApplicationController: 16 | 17 | include CentralLogger::Filter 18 | 19 | 1. If using Rails 3, SKIP this step. Otherwise, add the following to config/environment.rb: 20 | 21 | require 'central_logger' 22 | CentralLogger::Initializer.initialize_deprecated_logger(config) 23 | 24 | 1. Add mongo settings to database.yml for each environment in which you want to use the Central Logger. The central logger will also 25 | look for a separate central_logger.yml or mongoid.yml (if you are using mongoid) before looking in database.yml. 26 | In the central_logger.yml and mongoid.yml case, the settings should be defined without the 'mongo' subkey. 27 | 28 | database.yml: 29 | 30 | development: 31 | adapter: mysql 32 | database: my_app_development 33 | user: root 34 | mongo: 35 | database: my_app # required (the only required setting) 36 | capsize: <%= 10.megabytes %> # default: 250MB for production; 100MB otherwise 37 | host: localhost # default: localhost 38 | port: 27017 # default: 27017 39 | replica_set: true # default: false - Adds retries for ConnectionFailure during voting for replica set master 40 | safe_insert: true # default: false - Enable/Disable safe inserts (wait for insert to propagate to all nodes) 41 | application_name: my_app # default: Rails.application - Only really needed for non-capistrano Rails 2 deployments. Otherwise should set automatically. 42 | 43 | central_logger.yml: 44 | 45 | development: 46 | database: my_app 47 | capsize: <%= 10.megabytes %> 48 | host: localhost 49 | port: 27017 50 | replica_set: true 51 | 52 | With that in place, a new MongoDB document (record) will be created for each request and, 53 | by default will record the following information: Runtime, IP Address, Request Time, Controller, 54 | Action, Params, Application Name and All messages sent to the logger. The structure of the Mongo document looks like this: 55 | 56 | { 57 | 'action' : action_name, 58 | 'application_name' : application_name (rails root), 59 | 'controller' : controller_name, 60 | 'ip' : ip_address, 61 | 'messages' : { 62 | 'info' : [ ], 63 | 'debug' : [ ], 64 | 'error' : [ ], 65 | 'warn' : [ ], 66 | 'fatal' : [ ] 67 | }, 68 | 'params' : { }, 69 | 'path' : path, 70 | 'request_time' : date_of_request, 71 | 'runtime' : elapsed_execution_time_in_milliseconds, 72 | 'url' : full_url 73 | } 74 | 75 | Beyond that, if you want to add extra information to the base of the document 76 | (let's say something like user_guid on every request that it's available), 77 | you can just call the Rails.logger.add_metadata method on your logger like so 78 | (for example from a before_filter): 79 | 80 | # make sure we're using the CentralLogger in this environment 81 | if Rails.logger.respond_to?(:add_metadata) 82 | Rails.logger.add_metadata(:user_guid => @user_guid) 83 | end 84 | 85 | ## Central Log Viewer 86 | 87 | Please see the [central\_log\_viewer](https://github.com/customink/central_log_viewer): 88 | a companion web application for querying and viewing your centralized logs. 89 | 90 | ## Querying via the Rails console 91 | 92 | And now, for a couple quick examples on getting ahold of this log data... 93 | First, here's how to get a handle on the MongoDB from within a Rails console: 94 | 95 | >> db = Rails.logger.mongo_connection 96 | => #<Mongo::DB:0x102f19ac0 @slave_ok=nil, @name="my_app" ... > 97 | 98 | >> collection = db[Rails.logger.mongo_collection_name] 99 | => #<Mongo::Collection:0x1031b3ee8 @name="development_log" ... > 100 | 101 | Once you've got the collection, you can find all requests for a specific user (with guid): 102 | 103 | >> cursor = collection.find(:user_guid => '12355') 104 | => #<Mongo::Cursor:0x1031a3e30 ... > 105 | >> cursor.count 106 | => 5 107 | 108 | Find all requests that took more that one second to complete: 109 | 110 | >> collection.find({:runtime => {'$gt' => 1000}}).count 111 | => 3 112 | 113 | Find all order#show requests with a particular order id (id=order_id): 114 | 115 | >> collection.find({"controller" => "order", "action"=> "show", "params.id" => order_id}) 116 | 117 | Find all requests with an exception that contains "RoutingError" in the message or stack trace: 118 | 119 | >> collection.find({"messages.error" => /RoutingError/}) 120 | 121 | Find all requests with a request_date greater than '11/18/2010 22:59:52 GMT' 122 | 123 | >> collection.find({:request_time => {'$gt' => Time.utc(2010, 11, 18, 22, 59, 52)}}) 124 | 125 | Copyright (c) 2009-2011 Phil Burrows and CustomInk, released under the MIT license 126 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/testtask' 3 | require 'rake/rdoctask' 4 | require 'rubygems' 5 | require 'bundler' 6 | require 'jeweler' 7 | 8 | desc 'Default: run unit tests.' 9 | task :default => "test:units" 10 | task :test => "test:functionals" 11 | 12 | begin 13 | Bundler.setup(:default, :development) 14 | rescue Bundler::BundlerError => e 15 | $stderr.puts e.message 16 | $stderr.puts "Run `bundle install` to install missing gems" 17 | exit e.status_code 18 | end 19 | 20 | Jeweler::Tasks.new do |gem| 21 | gem.name = "central_logger" 22 | gem.summary = %Q{Central Logger for Rails} 23 | gem.description = %Q{Centralized logging for rails apps using MongoDB. The idea and the core code is from http://github.com/peburrows/central_logger} 24 | gem.email = "astupka@customink.com" 25 | gem.homepage = "http://github.com/customink/central_logger" 26 | gem.authors = ["Phil Burrows", "Alex Stupka"] 27 | gem.files.exclude 'test/rails/**/*' 28 | gem.test_files.exclude 'test/rails/**/*' 29 | end 30 | # dependencies defined in Gemfile 31 | 32 | def rake_functionals(opts=nil) 33 | if ENV['RUBYOPT'] 34 | # remove bundler/setup require that prematurely checks for gems and crashes 35 | ENV['RUBYOPT'] = ENV['RUBYOPT'].gsub(%r{-r\s*bundler/setup}, '') 36 | end 37 | # runs all the tests for each ruby version in each rails dir 38 | system("bash test/test.sh #{opts}") 39 | end 40 | 41 | namespace :test do 42 | desc "Run all tests against all permutations of ruby and rails" 43 | task :functionals do 44 | rake_functionals 45 | end 46 | 47 | namespace :functionals do 48 | desc "Clean out gemsets before running functional tests." 49 | task :clean do 50 | rake_functionals('--clean') 51 | end 52 | end 53 | 54 | desc "Run unit tests" 55 | Rake::TestTask.new(:units) do |test| 56 | test.libs << 'lib' << 'test' 57 | test.pattern = 'test/unit/central_logger_test.rb' 58 | test.verbose = true 59 | end 60 | 61 | desc "Run replica set tests" 62 | Rake::TestTask.new(:replica_set) do |test| 63 | test.libs << 'lib' << 'test' 64 | test.pattern = 'test/unit/central_logger_replica_test.rb' 65 | test.verbose = true 66 | end 67 | end 68 | 69 | Rake::RDocTask.new do |rdoc| 70 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 71 | 72 | rdoc.rdoc_dir = 'rdoc' 73 | rdoc.title = "central_logger #{version}" 74 | rdoc.rdoc_files.include('README*') 75 | rdoc.rdoc_files.include('lib/**/*.rb') 76 | end 77 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.3.2 -------------------------------------------------------------------------------- /central_logger.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{central_logger} 8 | s.version = "0.3.2" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Phil Burrows", "Alex Stupka"] 12 | s.date = %q{2011-03-15} 13 | s.description = %q{Centralized logging for rails apps using MongoDB. The idea and the core code is from http://github.com/peburrows/central_logger} 14 | s.email = %q{astupka@customink.com} 15 | s.extra_rdoc_files = [ 16 | "README.md" 17 | ] 18 | s.files = [ 19 | ".rvmrc", 20 | "Gemfile", 21 | "Gemfile.lock", 22 | "MIT-LICENSE", 23 | "README.md", 24 | "Rakefile", 25 | "VERSION", 26 | "central_logger.gemspec", 27 | "lib/central_logger.rb", 28 | "lib/central_logger/filter.rb", 29 | "lib/central_logger/initializer.rb", 30 | "lib/central_logger/initializer_mixin.rb", 31 | "lib/central_logger/mongo_logger.rb", 32 | "lib/central_logger/railtie.rb", 33 | "lib/central_logger/replica_set_helper.rb", 34 | "test/active_record.rb", 35 | "test/config/samples/central_logger.yml", 36 | "test/config/samples/database.yml", 37 | "test/config/samples/database_replica_set.yml", 38 | "test/config/samples/database_with_auth.yml", 39 | "test/config/samples/mongoid.yml", 40 | "test/rails.rb", 41 | "test/shoulda_macros/log_macros.rb", 42 | "test/test.sh", 43 | "test/test_helper.rb", 44 | "test/unit/central_logger_replica_test.rb", 45 | "test/unit/central_logger_test.rb" 46 | ] 47 | s.homepage = %q{http://github.com/customink/central_logger} 48 | s.require_paths = ["lib"] 49 | s.rubygems_version = %q{1.3.7} 50 | s.summary = %q{Central Logger for Rails} 51 | s.test_files = [ 52 | "test/active_record.rb", 53 | "test/rails.rb", 54 | "test/shoulda_macros/log_macros.rb", 55 | "test/test_helper.rb", 56 | "test/unit/central_logger_replica_test.rb", 57 | "test/unit/central_logger_test.rb" 58 | ] 59 | 60 | if s.respond_to? :specification_version then 61 | current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION 62 | s.specification_version = 3 63 | 64 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 65 | s.add_runtime_dependency(%q, [">= 0"]) 66 | s.add_runtime_dependency(%q, [">= 0"]) 67 | s.add_runtime_dependency(%q, [">= 0"]) 68 | s.add_runtime_dependency(%q, [">= 0"]) 69 | s.add_development_dependency(%q, ["~> 1.5.0.pre5"]) 70 | s.add_development_dependency(%q, [">= 0"]) 71 | s.add_development_dependency(%q, [">= 0"]) 72 | s.add_development_dependency(%q, [">= 0"]) 73 | s.add_development_dependency(%q, [">= 0"]) 74 | s.add_development_dependency(%q, [">= 0"]) 75 | else 76 | s.add_dependency(%q, [">= 0"]) 77 | s.add_dependency(%q, [">= 0"]) 78 | s.add_dependency(%q, [">= 0"]) 79 | s.add_dependency(%q, [">= 0"]) 80 | s.add_dependency(%q, ["~> 1.5.0.pre5"]) 81 | s.add_dependency(%q, [">= 0"]) 82 | s.add_dependency(%q, [">= 0"]) 83 | s.add_dependency(%q, [">= 0"]) 84 | s.add_dependency(%q, [">= 0"]) 85 | s.add_dependency(%q, [">= 0"]) 86 | end 87 | else 88 | s.add_dependency(%q, [">= 0"]) 89 | s.add_dependency(%q, [">= 0"]) 90 | s.add_dependency(%q, [">= 0"]) 91 | s.add_dependency(%q, [">= 0"]) 92 | s.add_dependency(%q, ["~> 1.5.0.pre5"]) 93 | s.add_dependency(%q, [">= 0"]) 94 | s.add_dependency(%q, [">= 0"]) 95 | s.add_dependency(%q, [">= 0"]) 96 | s.add_dependency(%q, [">= 0"]) 97 | s.add_dependency(%q, [">= 0"]) 98 | end 99 | end 100 | 101 | -------------------------------------------------------------------------------- /lib/central_logger.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.dirname(__FILE__) 2 | 3 | require 'mongo' 4 | require 'central_logger/mongo_logger' 5 | require 'central_logger/filter' 6 | require 'central_logger/initializer' 7 | require 'central_logger/railtie' 8 | 9 | -------------------------------------------------------------------------------- /lib/central_logger/filter.rb: -------------------------------------------------------------------------------- 1 | module CentralLogger 2 | module Filter 3 | def self.included(base) 4 | base.class_eval { around_filter :enable_central_logger } 5 | end 6 | 7 | def enable_central_logger 8 | return yield unless Rails.logger.respond_to?(:mongoize) 9 | 10 | # make sure the controller knows how to filter its parameters (Rails 3, 2, respectively) 11 | f_params = case 12 | when request.respond_to?(:filtered_parameters) then request.filtered_parameters 13 | when respond_to?(:filter_parameters) then filter_parameters(params) 14 | else params 15 | end 16 | Rails.logger.mongoize({ 17 | :action => action_name, 18 | :controller => controller_name, 19 | :path => request.path, 20 | :url => request.url, 21 | :params => f_params, 22 | :ip => request.remote_ip 23 | }) { yield } 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/central_logger/initializer.rb: -------------------------------------------------------------------------------- 1 | if Rails::VERSION::MAJOR == 2 2 | require 'central_logger/initializer_mixin' 3 | 4 | module CentralLogger 5 | class Initializer 6 | extend CentralLogger::InitializerMixin 7 | 8 | # mirrors code in Rails 2 initializer.rb#initialize_logger 9 | def self.initialize_deprecated_logger(config) 10 | logger = config.logger = create_logger(config, config.log_path) 11 | 12 | silence_warnings { Object.const_set "RAILS_DEFAULT_LOGGER", logger } 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/central_logger/initializer_mixin.rb: -------------------------------------------------------------------------------- 1 | module CentralLogger 2 | module InitializerMixin 3 | # initialization common to Rails 2.3.8 and 3.0 4 | def create_logger(config, path) 5 | level = ActiveSupport::BufferedLogger.const_get(config.log_level.to_s.upcase) 6 | logger = MongoLogger.new(:path => path, :level => level) 7 | logger.auto_flushing = false if Rails.env.production? 8 | logger 9 | rescue StandardError => e 10 | logger = ActiveSupport::BufferedLogger.new(STDERR) 11 | logger.level = ActiveSupport::BufferedLogger::WARN 12 | logger.warn( 13 | "CentralLogger Initializer Error: Unable to access log file. Please ensure that #{path} exists and is chmod 0666. " + 14 | "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." + "\n" + 15 | e.message + "\n" + e.backtrace.join("\n") 16 | ) 17 | logger 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/central_logger/mongo_logger.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | require 'mongo' 3 | require 'central_logger/replica_set_helper' 4 | 5 | module CentralLogger 6 | class MongoLogger < ActiveSupport::BufferedLogger 7 | include ReplicaSetHelper 8 | 9 | MB = 2 ** 20 10 | PRODUCTION_COLLECTION_SIZE = 256 * MB 11 | DEFAULT_COLLECTION_SIZE = 128 * MB 12 | # Looks for configuration files in this order 13 | CONFIGURATION_FILES = ["central_logger.yml", "mongoid.yml", "database.yml"] 14 | LOG_LEVEL_SYM = [:debug, :info, :warn, :error, :fatal, :unknown] 15 | 16 | attr_reader :db_configuration, :mongo_connection, :mongo_collection_name, :mongo_collection 17 | 18 | def initialize(options={}) 19 | path = options[:path] || File.join(Rails.root, "log/#{Rails.env}.log") 20 | level = options[:level] || DEBUG 21 | internal_initialize 22 | if disable_file_logging? 23 | @level = level 24 | @buffer = {} 25 | @auto_flushing = 1 26 | @guard = Mutex.new 27 | else 28 | super(path, level) 29 | end 30 | rescue => e 31 | # should use a config block for this 32 | Rails.env.production? ? (raise e) : (puts "Using BufferedLogger due to exception: " + e.message) 33 | end 34 | 35 | def add_metadata(options={}) 36 | options.each_pair do |key, value| 37 | unless [:messages, :request_time, :ip, :runtime, :application_name].include?(key.to_sym) 38 | @mongo_record[key] = value 39 | else 40 | raise ArgumentError, ":#{key} is a reserved key for the central logger. Please choose a different key" 41 | end 42 | end 43 | end 44 | 45 | def add(severity, message = nil, progname = nil, &block) 46 | if @level <= severity && message.present? && @mongo_record.present? 47 | # do not modify the original message used by the buffered logger 48 | msg = logging_colorized? ? message.to_s.gsub(/(\e(\[([\d;]*[mz]?))?)?/, '').strip : message 49 | @mongo_record[:messages][LOG_LEVEL_SYM[severity]] << msg 50 | end 51 | # may modify the original message 52 | disable_file_logging? ? message : super 53 | end 54 | 55 | # Drop the capped_collection and recreate it 56 | def reset_collection 57 | @mongo_collection.drop 58 | create_collection 59 | end 60 | 61 | def mongoize(options={}) 62 | @mongo_record = options.merge({ 63 | :messages => Hash.new { |hash, key| hash[key] = Array.new }, 64 | :request_time => Time.now.getutc, 65 | :application_name => @application_name 66 | }) 67 | 68 | runtime = Benchmark.measure{ yield }.real if block_given? 69 | rescue Exception => e 70 | add(3, e.message + "\n" + e.backtrace.join("\n")) 71 | # Reraise the exception for anyone else who cares 72 | raise e 73 | ensure 74 | # In case of exception, make sure runtime is set 75 | @mongo_record[:runtime] = ((runtime ||= 0) * 1000).ceil 76 | begin 77 | @insert_block.call 78 | rescue 79 | # do extra work to inpect (and flatten) 80 | force_serialize @mongo_record 81 | @insert_block.call rescue nil 82 | end 83 | end 84 | 85 | def authenticated? 86 | @authenticated 87 | end 88 | 89 | private 90 | # facilitate testing 91 | def internal_initialize 92 | configure 93 | connect 94 | check_for_collection 95 | end 96 | 97 | def disable_file_logging? 98 | @db_configuration.fetch('disable_file_logging', false) 99 | end 100 | 101 | def configure 102 | default_capsize = Rails.env.production? ? PRODUCTION_COLLECTION_SIZE : DEFAULT_COLLECTION_SIZE 103 | @mongo_collection_name = "#{Rails.env}_log" 104 | @authenticated = false 105 | @db_configuration = { 106 | 'host' => 'localhost', 107 | 'port' => 27017, 108 | 'capsize' => default_capsize}.merge(resolve_config) 109 | @application_name = resolve_application_name 110 | @safe_insert = @db_configuration['safe_insert'] || false 111 | 112 | @insert_block = @db_configuration.has_key?('replica_set') && @db_configuration['replica_set'] ? 113 | lambda { rescue_connection_failure{ insert_log_record(@safe_insert) } } : 114 | lambda { insert_log_record } 115 | end 116 | 117 | def resolve_application_name 118 | if @db_configuration.has_key?('application_name') 119 | @db_configuration['application_name'] 120 | elsif Rails::VERSION::MAJOR >= 3 121 | Rails.application.class.to_s.split("::").first 122 | else 123 | # rails 2 requires detective work if it's been deployed by capistrano 124 | # if last entry is a timestamp, go back 2 dirs (ex. /app_name/releases/20110304132847) 125 | path = Rails.root.to_s.split('/') 126 | path.length >= 4 && path.last =~ /^\d/ ? path.last(3)[0] : path.last 127 | end 128 | end 129 | 130 | def resolve_config 131 | config = {} 132 | CONFIGURATION_FILES.each do |filename| 133 | config_file = Rails.root.join("config", filename) 134 | if config_file.file? 135 | config = YAML.load(ERB.new(config_file.read).result)[Rails.env] 136 | config = config['mongo'] if config.has_key?('mongo') 137 | break 138 | end 139 | end 140 | config 141 | end 142 | 143 | def connect 144 | @mongo_connection ||= Mongo::Connection.new(@db_configuration['host'], 145 | @db_configuration['port']).db(@db_configuration['database']) 146 | 147 | if @db_configuration['username'] && @db_configuration['password'] 148 | # the driver stores credentials in case reconnection is required 149 | @authenticated = @mongo_connection.authenticate(@db_configuration['username'], 150 | @db_configuration['password']) 151 | end 152 | end 153 | 154 | def create_collection 155 | @mongo_connection.create_collection(@mongo_collection_name, 156 | {:capped => true, :size => @db_configuration['capsize'].to_i}) 157 | end 158 | 159 | def check_for_collection 160 | # setup the capped collection if it doesn't already exist 161 | unless @mongo_connection.collection_names.include?(@mongo_collection_name) 162 | create_collection 163 | end 164 | @mongo_collection = @mongo_connection[@mongo_collection_name] 165 | end 166 | 167 | def insert_log_record(safe=false) 168 | @mongo_collection.insert(@mongo_record, :safe => safe) 169 | end 170 | 171 | def logging_colorized? 172 | # Cache it since these ActiveRecord attributes are assigned after logger initialization occurs in Rails boot 173 | @colorized ||= Object.const_defined?(:ActiveRecord) && 174 | (Rails::VERSION::MAJOR >= 3 ? 175 | ActiveRecord::LogSubscriber.colorize_logging : 176 | ActiveRecord::Base.colorize_logging) 177 | end 178 | 179 | # force the data in the db by inspecting each top level array and hash element 180 | # this will flatten other hashes and arrays 181 | def force_serialize(rec) 182 | if msgs = rec[:messages] 183 | LOG_LEVEL_SYM.each do |i| 184 | msgs[i].collect! { |j| j.inspect } if msgs[i] 185 | end 186 | end 187 | if pms = rec[:params] 188 | pms.each { |i, j| pms[i] = j.inspect } 189 | end 190 | end 191 | end # class MongoLogger 192 | end 193 | -------------------------------------------------------------------------------- /lib/central_logger/railtie.rb: -------------------------------------------------------------------------------- 1 | if Rails::VERSION::MAJOR == 3 2 | require 'central_logger/initializer_mixin' 3 | 4 | class Railtie < Rails::Railtie 5 | include CentralLogger::InitializerMixin 6 | 7 | # load earlier than bootstrap.rb initializer loads the default logger. bootstrap 8 | # initializer will then skip its own initialization once Rails.logger is defined 9 | initializer :initialize_central_logger, :before => :initialize_logger do 10 | app_config = Rails.application.config 11 | Rails.logger = config.logger = create_logger(app_config, 12 | ((app_config.paths.log.to_a rescue nil) || app_config.paths['log']).first) 13 | end 14 | end 15 | end 16 | 17 | -------------------------------------------------------------------------------- /lib/central_logger/replica_set_helper.rb: -------------------------------------------------------------------------------- 1 | module CentralLogger 2 | module ReplicaSetHelper 3 | # Use retry alg from mongodb to gobble up connection failures during replica set master vote 4 | # Defaults to a 10 second wait 5 | def rescue_connection_failure(max_retries=40) 6 | success = false 7 | retries = 0 8 | while !success 9 | begin 10 | yield 11 | success = true 12 | rescue Mongo::ConnectionFailure => e 13 | raise e if (retries += 1) >= max_retries 14 | sleep 0.25 15 | end 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/active_record.rb: -------------------------------------------------------------------------------- 1 | module ActiveRecord 2 | class LogSubscriber 3 | def self.colorize_logging 4 | true 5 | end 6 | end 7 | 8 | class Base 9 | def self.colorize_logging 10 | true 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/config/samples/central_logger.yml: -------------------------------------------------------------------------------- 1 | test: 2 | database: system_log 3 | -------------------------------------------------------------------------------- /test/config/samples/database.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: mysql 3 | username: user 4 | database: database 5 | mongo: 6 | database: system_log 7 | application_name: central_foo 8 | safe_insert: true 9 | -------------------------------------------------------------------------------- /test/config/samples/database_no_file_logging.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: mysql 3 | username: user 4 | database: database 5 | mongo: 6 | database: system_log 7 | application_name: central_foo 8 | safe_insert: true 9 | disable_file_logging: true 10 | -------------------------------------------------------------------------------- /test/config/samples/database_replica_set.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: mysql 3 | username: user 4 | database: database 5 | mongo: 6 | database: system_log 7 | replica_set: true 8 | -------------------------------------------------------------------------------- /test/config/samples/database_with_auth.yml: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: mysql 3 | username: user 4 | database: database 5 | mongo: 6 | database: system_log 7 | username: admin 8 | password: password 9 | -------------------------------------------------------------------------------- /test/config/samples/mongoid.yml: -------------------------------------------------------------------------------- 1 | # taken from http://mongoid.org/docs/installation/ 2 | defaults: &defaults 3 | host: localhost 4 | slaves: 5 | - host: slave1.local 6 | port: 27018 7 | - host: slave2.local 8 | port: 27019 9 | autocreate_indexes: false 10 | allow_dynamic_fields: true 11 | include_root_in_json: false 12 | parameterize_keys: true 13 | persist_in_safe_mode: false 14 | raise_not_found_error: true 15 | reconnect_time: 3 16 | 17 | development: 18 | <<: *defaults 19 | database: control_development 20 | 21 | test: 22 | <<: *defaults 23 | database: system_log 24 | 25 | # set these environment variables on your prod server 26 | production: 27 | <<: *defaults 28 | host: <%= ENV['MONGOID_HOST'] %> 29 | port: <%= ENV['MONGOID_PORT'] %> 30 | database: <%= ENV['MONGOID_DATABASE'] %> 31 | 32 | -------------------------------------------------------------------------------- /test/rails.rb: -------------------------------------------------------------------------------- 1 | module CentralLogger 2 | class Application 3 | end 4 | end 5 | 6 | class Rails 7 | module VERSION 8 | MAJOR = 3 9 | end 10 | 11 | def self.env 12 | ActiveSupport::StringInquirer.new("test") 13 | end 14 | 15 | def self.root 16 | Pathname.new(File.dirname(__FILE__)) 17 | end 18 | 19 | def self.application 20 | CentralLogger::Application.new 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/rails/2/Gemfile: -------------------------------------------------------------------------------- 1 | # A sample Gemfile 2 | source "http://rubygems.org" 3 | 4 | gem 'sqlite3-ruby', :require => 'sqlite3' 5 | 6 | gem "rails", "2.3.8" 7 | gem "central_logger", :require => "central_logger", :path => "../../.." 8 | gem (RUBY_VERSION =~ /^1\.9/ ? "ruby-debug19" : "ruby-debug") 9 | -------------------------------------------------------------------------------- /test/rails/2/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../../.. 3 | specs: 4 | central_logger (0.3.1) 5 | bson_ext 6 | bundler 7 | mongo 8 | rake 9 | 10 | GEM 11 | remote: http://rubygems.org/ 12 | specs: 13 | actionmailer (2.3.8) 14 | actionpack (= 2.3.8) 15 | actionpack (2.3.8) 16 | activesupport (= 2.3.8) 17 | rack (~> 1.1.0) 18 | activerecord (2.3.8) 19 | activesupport (= 2.3.8) 20 | activeresource (2.3.8) 21 | activesupport (= 2.3.8) 22 | activesupport (2.3.8) 23 | archive-tar-minitar (0.5.2) 24 | bson (1.2.4) 25 | bson_ext (1.2.4) 26 | columnize (0.3.2) 27 | linecache19 (0.5.11) 28 | ruby_core_source (>= 0.1.4) 29 | mongo (1.2.4) 30 | bson (>= 1.2.4) 31 | rack (1.1.0) 32 | rails (2.3.8) 33 | actionmailer (= 2.3.8) 34 | actionpack (= 2.3.8) 35 | activerecord (= 2.3.8) 36 | activeresource (= 2.3.8) 37 | activesupport (= 2.3.8) 38 | rake (>= 0.8.3) 39 | rake (0.8.7) 40 | ruby-debug-base19 (0.11.24) 41 | columnize (>= 0.3.1) 42 | linecache19 (>= 0.5.11) 43 | ruby_core_source (>= 0.1.4) 44 | ruby-debug19 (0.11.6) 45 | columnize (>= 0.3.1) 46 | linecache19 (>= 0.5.11) 47 | ruby-debug-base19 (>= 0.11.19) 48 | ruby_core_source (0.1.4) 49 | archive-tar-minitar (>= 0.5.2) 50 | sqlite3-ruby (1.3.2) 51 | 52 | PLATFORMS 53 | ruby 54 | 55 | DEPENDENCIES 56 | central_logger! 57 | rails (= 2.3.8) 58 | ruby-debug19 59 | sqlite3-ruby 60 | -------------------------------------------------------------------------------- /test/rails/2/README: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" templates 7 | that are primarily responsible for inserting pre-built data in between HTML tags. 8 | The model contains the "smart" domain objects (such as Account, Product, Person, 9 | Post) that holds all the business logic and knows how to persist themselves to 10 | a database. The controller handles the incoming requests (such as Save New Account, 11 | Update Product, Show Post) by manipulating the model and directing data to the view. 12 | 13 | In Rails, the model is handled by what's called an object-relational mapping 14 | layer entitled Active Record. This layer allows you to present the data from 15 | database rows as objects and embellish these data objects with business logic 16 | methods. You can read more about Active Record in 17 | link:files/vendor/rails/activerecord/README.html. 18 | 19 | The controller and view are handled by the Action Pack, which handles both 20 | layers by its two parts: Action View and Action Controller. These two layers 21 | are bundled in a single package due to their heavy interdependence. This is 22 | unlike the relationship between the Active Record and Action Pack that is much 23 | more separate. Each of these packages can be used independently outside of 24 | Rails. You can read more about Action Pack in 25 | link:files/vendor/rails/actionpack/README.html. 26 | 27 | 28 | == Getting Started 29 | 30 | 1. At the command prompt, start a new Rails application using the rails command 31 | and your application name. Ex: rails myapp 32 | 2. Change directory into myapp and start the web server: script/server (run with --help for options) 33 | 3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!" 34 | 4. Follow the guidelines to start developing your application 35 | 36 | 37 | == Web Servers 38 | 39 | By default, Rails will try to use Mongrel if it's are installed when started with script/server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails 40 | with a variety of other web servers. 41 | 42 | Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is 43 | suitable for development and deployment of Rails applications. If you have Ruby Gems installed, 44 | getting up and running with mongrel is as easy as: gem install mongrel. 45 | More info at: http://mongrel.rubyforge.org 46 | 47 | Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or 48 | Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use 49 | FCGI or proxy to a pack of Mongrels/Thin/Ebb servers. 50 | 51 | == Apache .htaccess example for FCGI/CGI 52 | 53 | # General Apache options 54 | AddHandler fastcgi-script .fcgi 55 | AddHandler cgi-script .cgi 56 | Options +FollowSymLinks +ExecCGI 57 | 58 | # If you don't want Rails to look in certain directories, 59 | # use the following rewrite rules so that Apache won't rewrite certain requests 60 | # 61 | # Example: 62 | # RewriteCond %{REQUEST_URI} ^/notrails.* 63 | # RewriteRule .* - [L] 64 | 65 | # Redirect all requests not available on the filesystem to Rails 66 | # By default the cgi dispatcher is used which is very slow 67 | # 68 | # For better performance replace the dispatcher with the fastcgi one 69 | # 70 | # Example: 71 | # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] 72 | RewriteEngine On 73 | 74 | # If your Rails application is accessed via an Alias directive, 75 | # then you MUST also set the RewriteBase in this htaccess file. 76 | # 77 | # Example: 78 | # Alias /myrailsapp /path/to/myrailsapp/public 79 | # RewriteBase /myrailsapp 80 | 81 | RewriteRule ^$ index.html [QSA] 82 | RewriteRule ^([^.]+)$ $1.html [QSA] 83 | RewriteCond %{REQUEST_FILENAME} !-f 84 | RewriteRule ^(.*)$ dispatch.cgi [QSA,L] 85 | 86 | # In case Rails experiences terminal errors 87 | # Instead of displaying this message you can supply a file here which will be rendered instead 88 | # 89 | # Example: 90 | # ErrorDocument 500 /500.html 91 | 92 | ErrorDocument 500 "

Application error

Rails application failed to start properly" 93 | 94 | 95 | == Debugging Rails 96 | 97 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 98 | will help you debug it and get it back on the rails. 99 | 100 | First area to check is the application log files. Have "tail -f" commands running 101 | on the server.log and development.log. Rails will automatically display debugging 102 | and runtime information to these files. Debugging info will also be shown in the 103 | browser on requests from 127.0.0.1. 104 | 105 | You can also log your own messages directly into the log file from your code using 106 | the Ruby logger class from inside your controllers. Example: 107 | 108 | class WeblogController < ActionController::Base 109 | def destroy 110 | @weblog = Weblog.find(params[:id]) 111 | @weblog.destroy 112 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 113 | end 114 | end 115 | 116 | The result will be a message in your log file along the lines of: 117 | 118 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 119 | 120 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 121 | 122 | Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: 123 | 124 | * The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ 125 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 126 | 127 | These two online (and free) books will bring you up to speed on the Ruby language 128 | and also on programming in general. 129 | 130 | 131 | == Debugger 132 | 133 | Debugger support is available through the debugger command when you start your Mongrel or 134 | Webrick server with --debugger. This means that you can break out of execution at any point 135 | in the code, investigate and change the model, AND then resume execution! 136 | You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug' 137 | Example: 138 | 139 | class WeblogController < ActionController::Base 140 | def index 141 | @posts = Post.find(:all) 142 | debugger 143 | end 144 | end 145 | 146 | So the controller will accept the action, run the first line, then present you 147 | with a IRB prompt in the server window. Here you can do things like: 148 | 149 | >> @posts.inspect 150 | => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, 151 | #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" 152 | >> @posts.first.title = "hello from a debugger" 153 | => "hello from a debugger" 154 | 155 | ...and even better is that you can examine how your runtime objects actually work: 156 | 157 | >> f = @posts.first 158 | => #nil, "body"=>nil, "id"=>"1"}> 159 | >> f. 160 | Display all 152 possibilities? (y or n) 161 | 162 | Finally, when you're ready to resume execution, you enter "cont" 163 | 164 | 165 | == Console 166 | 167 | You can interact with the domain model by starting the console through script/console. 168 | Here you'll have all parts of the application configured, just like it is when the 169 | application is running. You can inspect domain models, change values, and save to the 170 | database. Starting the script without arguments will launch it in the development environment. 171 | Passing an argument will specify a different environment, like script/console production. 172 | 173 | To reload your controllers and models after launching the console run reload! 174 | 175 | == dbconsole 176 | 177 | You can go to the command line of your database directly through script/dbconsole. 178 | You would be connected to the database with the credentials defined in database.yml. 179 | Starting the script without arguments will connect you to the development database. Passing an 180 | argument will connect you to a different database, like script/dbconsole production. 181 | Currently works for mysql, postgresql and sqlite. 182 | 183 | == Description of Contents 184 | 185 | app 186 | Holds all the code that's specific to this particular application. 187 | 188 | app/controllers 189 | Holds controllers that should be named like weblogs_controller.rb for 190 | automated URL mapping. All controllers should descend from ApplicationController 191 | which itself descends from ActionController::Base. 192 | 193 | app/models 194 | Holds models that should be named like post.rb. 195 | Most models will descend from ActiveRecord::Base. 196 | 197 | app/views 198 | Holds the template files for the view that should be named like 199 | weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby 200 | syntax. 201 | 202 | app/views/layouts 203 | Holds the template files for layouts to be used with views. This models the common 204 | header/footer method of wrapping views. In your views, define a layout using the 205 | layout :default and create a file named default.html.erb. Inside default.html.erb, 206 | call <% yield %> to render the view using this layout. 207 | 208 | app/helpers 209 | Holds view helpers that should be named like weblogs_helper.rb. These are generated 210 | for you automatically when using script/generate for controllers. Helpers can be used to 211 | wrap functionality for your views into methods. 212 | 213 | config 214 | Configuration files for the Rails environment, the routing map, the database, and other dependencies. 215 | 216 | db 217 | Contains the database schema in schema.rb. db/migrate contains all 218 | the sequence of Migrations for your schema. 219 | 220 | doc 221 | This directory is where your application documentation will be stored when generated 222 | using rake doc:app 223 | 224 | lib 225 | Application specific libraries. Basically, any kind of custom code that doesn't 226 | belong under controllers, models, or helpers. This directory is in the load path. 227 | 228 | public 229 | The directory available for the web server. Contains subdirectories for images, stylesheets, 230 | and javascripts. Also contains the dispatchers and the default HTML files. This should be 231 | set as the DOCUMENT_ROOT of your web server. 232 | 233 | script 234 | Helper scripts for automation and generation. 235 | 236 | test 237 | Unit and functional tests along with fixtures. When using the script/generate scripts, template 238 | test files will be generated for you and placed in this directory. 239 | 240 | vendor 241 | External libraries that the application depends on. Also includes the plugins subdirectory. 242 | If the app has frozen rails, those gems also go here, under vendor/rails/. 243 | This directory is in the load path. 244 | -------------------------------------------------------------------------------- /test/rails/2/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require(File.join(File.dirname(__FILE__), 'config', 'boot')) 5 | 6 | require 'rake' 7 | require 'rake/testtask' 8 | require 'rake/rdoctask' 9 | 10 | require 'tasks/rails' 11 | -------------------------------------------------------------------------------- /test/rails/2/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # Filters added to this controller apply to all controllers in the application. 2 | # Likewise, all the methods added will be available for all controllers. 3 | 4 | class ApplicationController < ActionController::Base 5 | include CentralLogger::Filter 6 | helper :all # include all helpers, all the time 7 | protect_from_forgery # See ActionController::RequestForgeryProtection for details 8 | 9 | # Scrub sensitive parameters from your log 10 | filter_parameter_logging :password 11 | end 12 | -------------------------------------------------------------------------------- /test/rails/2/app/controllers/order_controller.rb: -------------------------------------------------------------------------------- 1 | ../../../common/app/controllers/order_controller.rb -------------------------------------------------------------------------------- /test/rails/2/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # Methods added to this helper will be available to all templates in the application. 2 | module ApplicationHelper 3 | end 4 | -------------------------------------------------------------------------------- /test/rails/2/app/views: -------------------------------------------------------------------------------- 1 | ../../common/app/views -------------------------------------------------------------------------------- /test/rails/2/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Don't change this file! 2 | # Configure your app in config/environment.rb and config/environments/*.rb 3 | 4 | RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) 5 | 6 | module Rails 7 | class << self 8 | def boot! 9 | unless booted? 10 | preinitialize 11 | pick_boot.run 12 | end 13 | end 14 | 15 | def booted? 16 | defined? Rails::Initializer 17 | end 18 | 19 | def pick_boot 20 | (vendor_rails? ? VendorBoot : GemBoot).new 21 | end 22 | 23 | def vendor_rails? 24 | File.exist?("#{RAILS_ROOT}/vendor/rails") 25 | end 26 | 27 | def preinitialize 28 | load(preinitializer_path) if File.exist?(preinitializer_path) 29 | end 30 | 31 | def preinitializer_path 32 | "#{RAILS_ROOT}/config/preinitializer.rb" 33 | end 34 | end 35 | 36 | class Boot 37 | def run 38 | load_initializer 39 | Rails::Initializer.run(:set_load_path) 40 | end 41 | end 42 | 43 | class VendorBoot < Boot 44 | def load_initializer 45 | require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" 46 | Rails::Initializer.run(:install_gem_spec_stubs) 47 | Rails::GemDependency.add_frozen_gem_path 48 | end 49 | end 50 | 51 | class GemBoot < Boot 52 | def load_initializer 53 | self.class.load_rubygems 54 | load_rails_gem 55 | require 'initializer' 56 | end 57 | 58 | def load_rails_gem 59 | if version = self.class.gem_version 60 | gem 'rails', version 61 | else 62 | gem 'rails' 63 | end 64 | rescue Gem::LoadError => load_error 65 | $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.) 66 | exit 1 67 | end 68 | 69 | class << self 70 | def rubygems_version 71 | Gem::RubyGemsVersion rescue nil 72 | end 73 | 74 | def gem_version 75 | if defined? RAILS_GEM_VERSION 76 | RAILS_GEM_VERSION 77 | elsif ENV.include?('RAILS_GEM_VERSION') 78 | ENV['RAILS_GEM_VERSION'] 79 | else 80 | parse_gem_version(read_environment_rb) 81 | end 82 | end 83 | 84 | def load_rubygems 85 | min_version = '1.3.2' 86 | require 'rubygems' 87 | unless rubygems_version >= min_version 88 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) 89 | exit 1 90 | end 91 | 92 | rescue LoadError 93 | $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) 94 | exit 1 95 | end 96 | 97 | def parse_gem_version(text) 98 | $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ 99 | end 100 | 101 | private 102 | def read_environment_rb 103 | File.read("#{RAILS_ROOT}/config/environment.rb") 104 | end 105 | end 106 | end 107 | end 108 | 109 | # All that for this: 110 | Rails.boot! 111 | -------------------------------------------------------------------------------- /test/rails/2/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3-ruby (not necessary on OS X Leopard) 3 | development: 4 | database: dev_db 5 | adapter: sqlite3 6 | mongo: 7 | database: logger_and_rails # required 8 | capsize: <%= 10.megabytes %> # default: 250MB for production; 100MB otherwise 9 | host: localhost # default: localhost 10 | port: 27017 # default: 27017 11 | 12 | # adapter: sqlite3 13 | # database: db/development.sqlite3 14 | # pool: 5 15 | # timeout: 5000 16 | 17 | # Warning: The database defined as "test" will be erased and 18 | # re-generated from your development database when you run "rake". 19 | # Do not set this db to the same as development or production. 20 | test: 21 | adapter: sqlite3 22 | database: test_db 23 | mongo: 24 | database: logger_and_rails # required 25 | capsize: <%= 10.megabytes %> # default: 250MB for production; 100MB otherwise 26 | host: localhost # default: localhost 27 | port: 27017 # default: 27017 28 | -------------------------------------------------------------------------------- /test/rails/2/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file 2 | 3 | # Specifies gem version of Rails to use when vendor/rails is not present 4 | RAILS_GEM_VERSION = '2.3.8' unless defined? RAILS_GEM_VERSION 5 | 6 | # Bootstrap the Rails environment, frameworks, and default configuration 7 | require File.join(File.dirname(__FILE__), 'boot') 8 | 9 | Rails::Initializer.run do |config| 10 | require 'central_logger' 11 | CentralLogger::Initializer.initialize_deprecated_logger(config) 12 | # Settings in config/environments/* take precedence over those specified here. 13 | # Application configuration should go into files in config/initializers 14 | # -- all .rb files in that directory are automatically loaded. 15 | 16 | # Add additional load paths for your own custom dirs 17 | # config.load_paths += %W( #{RAILS_ROOT}/extras ) 18 | 19 | # Specify gems that this application depends on and have them installed with rake gems:install 20 | # config.gem "bj" 21 | # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" 22 | # config.gem "sqlite3-ruby", :lib => "sqlite3" 23 | # config.gem "aws-s3", :lib => "aws/s3" 24 | 25 | # Only load the plugins named here, in the order given (default is alphabetical). 26 | # :all can be used as a placeholder for all plugins not explicitly named 27 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 28 | 29 | # Skip frameworks you're not going to use. To use Rails without a database, 30 | # you must remove the Active Record framework. 31 | # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] 32 | 33 | # Activate observers that should always be running 34 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 35 | 36 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 37 | # Run "rake -D time" for a list of tasks for finding time zone names. 38 | config.time_zone = 'UTC' 39 | 40 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 41 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] 42 | # config.i18n.default_locale = :de 43 | end 44 | -------------------------------------------------------------------------------- /test/rails/2/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # In the development environment your application's code is reloaded on 4 | # every request. This slows down response time but is perfect for development 5 | # since you don't have to restart the webserver when you make code changes. 6 | config.cache_classes = false 7 | 8 | # Log error messages when you accidentally call methods on nil. 9 | config.whiny_nils = true 10 | 11 | # Show full error reports and disable caching 12 | config.action_controller.consider_all_requests_local = true 13 | config.action_view.debug_rjs = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false -------------------------------------------------------------------------------- /test/rails/2/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # The production environment is meant for finished, "live" apps. 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.action_controller.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | config.action_view.cache_template_loading = true 11 | 12 | # See everything in the log (default is :info) 13 | # config.log_level = :debug 14 | 15 | # Use a different logger for distributed setups 16 | # config.logger = SyslogLogger.new 17 | 18 | # Use a different cache store in production 19 | # config.cache_store = :mem_cache_store 20 | 21 | # Enable serving of images, stylesheets, and javascripts from an asset server 22 | # config.action_controller.asset_host = "http://assets.example.com" 23 | 24 | # Disable delivery errors, bad email addresses will be ignored 25 | # config.action_mailer.raise_delivery_errors = false 26 | 27 | # Enable threaded mode 28 | # config.threadsafe! -------------------------------------------------------------------------------- /test/rails/2/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # Settings specified here will take precedence over those in config/environment.rb 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | config.cache_classes = true 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.action_controller.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | config.action_view.cache_template_loading = true 16 | 17 | # Disable request forgery protection in test environment 18 | config.action_controller.allow_forgery_protection = false 19 | 20 | # Tell Action Mailer not to deliver emails to the real world. 21 | # The :test delivery method accumulates sent emails in the 22 | # ActionMailer::Base.deliveries array. 23 | config.action_mailer.delivery_method = :test 24 | 25 | # Use SQL instead of Active Record's schema dumper when creating the test database. 26 | # This is necessary if your schema can't be completely dumped by the schema dumper, 27 | # like if you have constraints or database-specific column types 28 | # config.active_record.schema_format = :sql -------------------------------------------------------------------------------- /test/rails/2/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying do debug a problem that might steem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! -------------------------------------------------------------------------------- /test/rails/2/config/initializers/cookie_verification_secret.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | ActionController::Base.cookie_verifier_secret = 'f8644c5fbcc0eee074d31ed22d0af8edcf3216a9f9daeb5452cc8ee168aac362ffab16e6678dac68967f411aac04bb22936013b3c4b2d1d201d88beada7afac9'; 8 | -------------------------------------------------------------------------------- /test/rails/2/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /test/rails/2/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /test/rails/2/config/initializers/new_rails_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # These settings change the behavior of Rails 2 apps and will be defaults 4 | # for Rails 3. You can remove this initializer when Rails 3 is released. 5 | 6 | if defined?(ActiveRecord) 7 | # Include Active Record class name as root for JSON serialized output. 8 | ActiveRecord::Base.include_root_in_json = true 9 | 10 | # Store the full class name (including module namespace) in STI type column. 11 | ActiveRecord::Base.store_full_sti_class = true 12 | end 13 | 14 | ActionController::Routing.generate_best_match = false 15 | 16 | # Use ISO 8601 format for JSON serialized times and dates. 17 | ActiveSupport.use_standard_json_time_format = true 18 | 19 | # Don't escape HTML entities in JSON, leave that for the #json_escape helper. 20 | # if you're including raw json in an HTML page. 21 | ActiveSupport.escape_html_entities_in_json = false -------------------------------------------------------------------------------- /test/rails/2/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying cookie session data integrity. 4 | # If you change this key, all old sessions will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | ActionController::Base.session = { 8 | :key => '_logger_and_rails2_session', 9 | :secret => '7c2319b2b268596a89acfe8e58e0cb87f52a13fbe2e29cc0ab26bf060fd8dc221887849b42dbc12bfffc587dc7746db08101b164bd821b6a38038a7b381206b4' 10 | } 11 | 12 | # Use the database for sessions instead of the cookie-based default, 13 | # which shouldn't be used to store highly confidential information 14 | # (create the session table with "rake db:sessions:create") 15 | # ActionController::Base.session_store = :active_record_store 16 | -------------------------------------------------------------------------------- /test/rails/2/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" -------------------------------------------------------------------------------- /test/rails/2/config/preinitializer.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require "rubygems" 3 | require "bundler" 4 | rescue LoadError 5 | raise "Could not load the bundler gem. Install it with `gem install bundler`." 6 | end 7 | 8 | if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24") 9 | raise RuntimeError, "Your bundler version is too old for Rails 2.3." + 10 | "Run `gem install bundler` to upgrade." 11 | end 12 | 13 | begin 14 | # Set up load paths for all bundled gems 15 | ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__) 16 | Bundler.setup 17 | rescue Bundler::GemNotFound 18 | raise RuntimeError, "Bundler couldn't find some gems." + 19 | "Did you run `bundle install`?" 20 | end 21 | 22 | -------------------------------------------------------------------------------- /test/rails/2/config/routes.rb: -------------------------------------------------------------------------------- 1 | ActionController::Routing::Routes.draw do |map| 2 | # The priority is based upon order of creation: first created -> highest priority. 3 | 4 | # Sample of regular route: 5 | # map.connect 'products/:id', :controller => 'catalog', :action => 'view' 6 | # Keep in mind you can assign values other than :controller and :action 7 | 8 | # Sample of named route: 9 | # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' 10 | # This route can be invoked with purchase_url(:id => product.id) 11 | 12 | # Sample resource route (maps HTTP verbs to controller actions automatically): 13 | # map.resources :products 14 | 15 | # Sample resource route with options: 16 | # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get } 17 | 18 | # Sample resource route with sub-resources: 19 | # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller 20 | 21 | # Sample resource route with more complex sub-resources 22 | # map.resources :products do |products| 23 | # products.resources :comments 24 | # products.resources :sales, :collection => { :recent => :get } 25 | # end 26 | 27 | # Sample resource route within a namespace: 28 | # map.namespace :admin do |admin| 29 | # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb) 30 | # admin.resources :products 31 | # end 32 | 33 | # You can have the root of your site routed with map.root -- just remember to delete public/index.html. 34 | # map.root :controller => "welcome" 35 | 36 | # See how all your routes lay out with "rake routes" 37 | 38 | # Install the default routes as the lowest priority. 39 | # Note: These default routes make all actions in every controller accessible via GET requests. You should 40 | # consider removing or commenting them out if you're using named routes and resources. 41 | map.connect ':controller/:action/:id' 42 | map.connect ':controller/:action/:id.:format' 43 | end 44 | -------------------------------------------------------------------------------- /test/rails/2/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead of editing this file, 2 | # please use the migrations feature of Active Record to incrementally modify your database, and 3 | # then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your database schema. If you need 6 | # to create the application database on another system, you should be using db:schema:load, not running 7 | # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations 8 | # you'll amass, the slower it'll run and the greater likelihood for issues). 9 | # 10 | # It's strongly recommended to check this file into your version control system. 11 | 12 | ActiveRecord::Schema.define(:version => 0) do 13 | 14 | end 15 | -------------------------------------------------------------------------------- /test/rails/2/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) 7 | # Major.create(:name => 'Daley', :city => cities.first) 8 | -------------------------------------------------------------------------------- /test/rails/2/doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /test/rails/2/public/404.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | The page you were looking for doesn't exist (404) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

The page you were looking for doesn't exist.

27 |

You may have mistyped the address or the page may have moved.

28 |
29 | 30 | -------------------------------------------------------------------------------- /test/rails/2/public/422.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | The change you wanted was rejected (422) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

The change you wanted was rejected.

27 |

Maybe you tried to change something you didn't have access to.

28 |
29 | 30 | -------------------------------------------------------------------------------- /test/rails/2/public/500.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | We're sorry, but something went wrong (500) 9 | 21 | 22 | 23 | 24 | 25 |
26 |

We're sorry, but something went wrong.

27 |

We've been notified about this issue and we'll take a look at it shortly.

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /test/rails/2/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/customink/central_logger/72bba43d1990a4ba38b126749f1feb77ea108a4a/test/rails/2/public/favicon.ico -------------------------------------------------------------------------------- /test/rails/2/public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/customink/central_logger/72bba43d1990a4ba38b126749f1feb77ea108a4a/test/rails/2/public/images/rails.png -------------------------------------------------------------------------------- /test/rails/2/public/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Ruby on Rails: Welcome aboard 7 | 181 | 182 | 183 | 204 | 205 | 206 |
207 | 237 | 238 |
239 | 243 | 244 | 248 | 249 |
250 |

Getting started

251 |

Here’s how to get rolling:

252 | 253 |
    254 |
  1. 255 |

    Use script/generate to create your models and controllers

    256 |

    To see all available options, run it without parameters.

    257 |
  2. 258 | 259 |
  3. 260 |

    Set up a default route and remove or rename this file

    261 |

    Routes are set up in config/routes.rb.

    262 |
  4. 263 | 264 |
  5. 265 |

    Create your database

    266 |

    Run rake db:migrate to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.

    267 |
  6. 268 |
269 |
270 |
271 | 272 | 273 |
274 | 275 | -------------------------------------------------------------------------------- /test/rails/2/public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // Place your application-specific JavaScript functions and classes here 2 | // This file is automatically included by javascript_include_tag :defaults 3 | -------------------------------------------------------------------------------- /test/rails/2/public/javascripts/dragdrop.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 2 | // (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) 3 | // 4 | // script.aculo.us is freely distributable under the terms of an MIT-style license. 5 | // For details, see the script.aculo.us web site: http://script.aculo.us/ 6 | 7 | if(Object.isUndefined(Effect)) 8 | throw("dragdrop.js requires including script.aculo.us' effects.js library"); 9 | 10 | var Droppables = { 11 | drops: [], 12 | 13 | remove: function(element) { 14 | this.drops = this.drops.reject(function(d) { return d.element==$(element) }); 15 | }, 16 | 17 | add: function(element) { 18 | element = $(element); 19 | var options = Object.extend({ 20 | greedy: true, 21 | hoverclass: null, 22 | tree: false 23 | }, arguments[1] || { }); 24 | 25 | // cache containers 26 | if(options.containment) { 27 | options._containers = []; 28 | var containment = options.containment; 29 | if(Object.isArray(containment)) { 30 | containment.each( function(c) { options._containers.push($(c)) }); 31 | } else { 32 | options._containers.push($(containment)); 33 | } 34 | } 35 | 36 | if(options.accept) options.accept = [options.accept].flatten(); 37 | 38 | Element.makePositioned(element); // fix IE 39 | options.element = element; 40 | 41 | this.drops.push(options); 42 | }, 43 | 44 | findDeepestChild: function(drops) { 45 | deepest = drops[0]; 46 | 47 | for (i = 1; i < drops.length; ++i) 48 | if (Element.isParent(drops[i].element, deepest.element)) 49 | deepest = drops[i]; 50 | 51 | return deepest; 52 | }, 53 | 54 | isContained: function(element, drop) { 55 | var containmentNode; 56 | if(drop.tree) { 57 | containmentNode = element.treeNode; 58 | } else { 59 | containmentNode = element.parentNode; 60 | } 61 | return drop._containers.detect(function(c) { return containmentNode == c }); 62 | }, 63 | 64 | isAffected: function(point, element, drop) { 65 | return ( 66 | (drop.element!=element) && 67 | ((!drop._containers) || 68 | this.isContained(element, drop)) && 69 | ((!drop.accept) || 70 | (Element.classNames(element).detect( 71 | function(v) { return drop.accept.include(v) } ) )) && 72 | Position.within(drop.element, point[0], point[1]) ); 73 | }, 74 | 75 | deactivate: function(drop) { 76 | if(drop.hoverclass) 77 | Element.removeClassName(drop.element, drop.hoverclass); 78 | this.last_active = null; 79 | }, 80 | 81 | activate: function(drop) { 82 | if(drop.hoverclass) 83 | Element.addClassName(drop.element, drop.hoverclass); 84 | this.last_active = drop; 85 | }, 86 | 87 | show: function(point, element) { 88 | if(!this.drops.length) return; 89 | var drop, affected = []; 90 | 91 | this.drops.each( function(drop) { 92 | if(Droppables.isAffected(point, element, drop)) 93 | affected.push(drop); 94 | }); 95 | 96 | if(affected.length>0) 97 | drop = Droppables.findDeepestChild(affected); 98 | 99 | if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); 100 | if (drop) { 101 | Position.within(drop.element, point[0], point[1]); 102 | if(drop.onHover) 103 | drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 104 | 105 | if (drop != this.last_active) Droppables.activate(drop); 106 | } 107 | }, 108 | 109 | fire: function(event, element) { 110 | if(!this.last_active) return; 111 | Position.prepare(); 112 | 113 | if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) 114 | if (this.last_active.onDrop) { 115 | this.last_active.onDrop(element, this.last_active.element, event); 116 | return true; 117 | } 118 | }, 119 | 120 | reset: function() { 121 | if(this.last_active) 122 | this.deactivate(this.last_active); 123 | } 124 | }; 125 | 126 | var Draggables = { 127 | drags: [], 128 | observers: [], 129 | 130 | register: function(draggable) { 131 | if(this.drags.length == 0) { 132 | this.eventMouseUp = this.endDrag.bindAsEventListener(this); 133 | this.eventMouseMove = this.updateDrag.bindAsEventListener(this); 134 | this.eventKeypress = this.keyPress.bindAsEventListener(this); 135 | 136 | Event.observe(document, "mouseup", this.eventMouseUp); 137 | Event.observe(document, "mousemove", this.eventMouseMove); 138 | Event.observe(document, "keypress", this.eventKeypress); 139 | } 140 | this.drags.push(draggable); 141 | }, 142 | 143 | unregister: function(draggable) { 144 | this.drags = this.drags.reject(function(d) { return d==draggable }); 145 | if(this.drags.length == 0) { 146 | Event.stopObserving(document, "mouseup", this.eventMouseUp); 147 | Event.stopObserving(document, "mousemove", this.eventMouseMove); 148 | Event.stopObserving(document, "keypress", this.eventKeypress); 149 | } 150 | }, 151 | 152 | activate: function(draggable) { 153 | if(draggable.options.delay) { 154 | this._timeout = setTimeout(function() { 155 | Draggables._timeout = null; 156 | window.focus(); 157 | Draggables.activeDraggable = draggable; 158 | }.bind(this), draggable.options.delay); 159 | } else { 160 | window.focus(); // allows keypress events if window isn't currently focused, fails for Safari 161 | this.activeDraggable = draggable; 162 | } 163 | }, 164 | 165 | deactivate: function() { 166 | this.activeDraggable = null; 167 | }, 168 | 169 | updateDrag: function(event) { 170 | if(!this.activeDraggable) return; 171 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 172 | // Mozilla-based browsers fire successive mousemove events with 173 | // the same coordinates, prevent needless redrawing (moz bug?) 174 | if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; 175 | this._lastPointer = pointer; 176 | 177 | this.activeDraggable.updateDrag(event, pointer); 178 | }, 179 | 180 | endDrag: function(event) { 181 | if(this._timeout) { 182 | clearTimeout(this._timeout); 183 | this._timeout = null; 184 | } 185 | if(!this.activeDraggable) return; 186 | this._lastPointer = null; 187 | this.activeDraggable.endDrag(event); 188 | this.activeDraggable = null; 189 | }, 190 | 191 | keyPress: function(event) { 192 | if(this.activeDraggable) 193 | this.activeDraggable.keyPress(event); 194 | }, 195 | 196 | addObserver: function(observer) { 197 | this.observers.push(observer); 198 | this._cacheObserverCallbacks(); 199 | }, 200 | 201 | removeObserver: function(element) { // element instead of observer fixes mem leaks 202 | this.observers = this.observers.reject( function(o) { return o.element==element }); 203 | this._cacheObserverCallbacks(); 204 | }, 205 | 206 | notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' 207 | if(this[eventName+'Count'] > 0) 208 | this.observers.each( function(o) { 209 | if(o[eventName]) o[eventName](eventName, draggable, event); 210 | }); 211 | if(draggable.options[eventName]) draggable.options[eventName](draggable, event); 212 | }, 213 | 214 | _cacheObserverCallbacks: function() { 215 | ['onStart','onEnd','onDrag'].each( function(eventName) { 216 | Draggables[eventName+'Count'] = Draggables.observers.select( 217 | function(o) { return o[eventName]; } 218 | ).length; 219 | }); 220 | } 221 | }; 222 | 223 | /*--------------------------------------------------------------------------*/ 224 | 225 | var Draggable = Class.create({ 226 | initialize: function(element) { 227 | var defaults = { 228 | handle: false, 229 | reverteffect: function(element, top_offset, left_offset) { 230 | var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; 231 | new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, 232 | queue: {scope:'_draggable', position:'end'} 233 | }); 234 | }, 235 | endeffect: function(element) { 236 | var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; 237 | new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 238 | queue: {scope:'_draggable', position:'end'}, 239 | afterFinish: function(){ 240 | Draggable._dragging[element] = false 241 | } 242 | }); 243 | }, 244 | zindex: 1000, 245 | revert: false, 246 | quiet: false, 247 | scroll: false, 248 | scrollSensitivity: 20, 249 | scrollSpeed: 15, 250 | snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } 251 | delay: 0 252 | }; 253 | 254 | if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) 255 | Object.extend(defaults, { 256 | starteffect: function(element) { 257 | element._opacity = Element.getOpacity(element); 258 | Draggable._dragging[element] = true; 259 | new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 260 | } 261 | }); 262 | 263 | var options = Object.extend(defaults, arguments[1] || { }); 264 | 265 | this.element = $(element); 266 | 267 | if(options.handle && Object.isString(options.handle)) 268 | this.handle = this.element.down('.'+options.handle, 0); 269 | 270 | if(!this.handle) this.handle = $(options.handle); 271 | if(!this.handle) this.handle = this.element; 272 | 273 | if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { 274 | options.scroll = $(options.scroll); 275 | this._isScrollChild = Element.childOf(this.element, options.scroll); 276 | } 277 | 278 | Element.makePositioned(this.element); // fix IE 279 | 280 | this.options = options; 281 | this.dragging = false; 282 | 283 | this.eventMouseDown = this.initDrag.bindAsEventListener(this); 284 | Event.observe(this.handle, "mousedown", this.eventMouseDown); 285 | 286 | Draggables.register(this); 287 | }, 288 | 289 | destroy: function() { 290 | Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); 291 | Draggables.unregister(this); 292 | }, 293 | 294 | currentDelta: function() { 295 | return([ 296 | parseInt(Element.getStyle(this.element,'left') || '0'), 297 | parseInt(Element.getStyle(this.element,'top') || '0')]); 298 | }, 299 | 300 | initDrag: function(event) { 301 | if(!Object.isUndefined(Draggable._dragging[this.element]) && 302 | Draggable._dragging[this.element]) return; 303 | if(Event.isLeftClick(event)) { 304 | // abort on form elements, fixes a Firefox issue 305 | var src = Event.element(event); 306 | if((tag_name = src.tagName.toUpperCase()) && ( 307 | tag_name=='INPUT' || 308 | tag_name=='SELECT' || 309 | tag_name=='OPTION' || 310 | tag_name=='BUTTON' || 311 | tag_name=='TEXTAREA')) return; 312 | 313 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 314 | var pos = Position.cumulativeOffset(this.element); 315 | this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); 316 | 317 | Draggables.activate(this); 318 | Event.stop(event); 319 | } 320 | }, 321 | 322 | startDrag: function(event) { 323 | this.dragging = true; 324 | if(!this.delta) 325 | this.delta = this.currentDelta(); 326 | 327 | if(this.options.zindex) { 328 | this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 329 | this.element.style.zIndex = this.options.zindex; 330 | } 331 | 332 | if(this.options.ghosting) { 333 | this._clone = this.element.cloneNode(true); 334 | this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); 335 | if (!this._originallyAbsolute) 336 | Position.absolutize(this.element); 337 | this.element.parentNode.insertBefore(this._clone, this.element); 338 | } 339 | 340 | if(this.options.scroll) { 341 | if (this.options.scroll == window) { 342 | var where = this._getWindowScroll(this.options.scroll); 343 | this.originalScrollLeft = where.left; 344 | this.originalScrollTop = where.top; 345 | } else { 346 | this.originalScrollLeft = this.options.scroll.scrollLeft; 347 | this.originalScrollTop = this.options.scroll.scrollTop; 348 | } 349 | } 350 | 351 | Draggables.notify('onStart', this, event); 352 | 353 | if(this.options.starteffect) this.options.starteffect(this.element); 354 | }, 355 | 356 | updateDrag: function(event, pointer) { 357 | if(!this.dragging) this.startDrag(event); 358 | 359 | if(!this.options.quiet){ 360 | Position.prepare(); 361 | Droppables.show(pointer, this.element); 362 | } 363 | 364 | Draggables.notify('onDrag', this, event); 365 | 366 | this.draw(pointer); 367 | if(this.options.change) this.options.change(this); 368 | 369 | if(this.options.scroll) { 370 | this.stopScrolling(); 371 | 372 | var p; 373 | if (this.options.scroll == window) { 374 | with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } 375 | } else { 376 | p = Position.page(this.options.scroll); 377 | p[0] += this.options.scroll.scrollLeft + Position.deltaX; 378 | p[1] += this.options.scroll.scrollTop + Position.deltaY; 379 | p.push(p[0]+this.options.scroll.offsetWidth); 380 | p.push(p[1]+this.options.scroll.offsetHeight); 381 | } 382 | var speed = [0,0]; 383 | if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); 384 | if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); 385 | if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); 386 | if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); 387 | this.startScrolling(speed); 388 | } 389 | 390 | // fix AppleWebKit rendering 391 | if(Prototype.Browser.WebKit) window.scrollBy(0,0); 392 | 393 | Event.stop(event); 394 | }, 395 | 396 | finishDrag: function(event, success) { 397 | this.dragging = false; 398 | 399 | if(this.options.quiet){ 400 | Position.prepare(); 401 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 402 | Droppables.show(pointer, this.element); 403 | } 404 | 405 | if(this.options.ghosting) { 406 | if (!this._originallyAbsolute) 407 | Position.relativize(this.element); 408 | delete this._originallyAbsolute; 409 | Element.remove(this._clone); 410 | this._clone = null; 411 | } 412 | 413 | var dropped = false; 414 | if(success) { 415 | dropped = Droppables.fire(event, this.element); 416 | if (!dropped) dropped = false; 417 | } 418 | if(dropped && this.options.onDropped) this.options.onDropped(this.element); 419 | Draggables.notify('onEnd', this, event); 420 | 421 | var revert = this.options.revert; 422 | if(revert && Object.isFunction(revert)) revert = revert(this.element); 423 | 424 | var d = this.currentDelta(); 425 | if(revert && this.options.reverteffect) { 426 | if (dropped == 0 || revert != 'failure') 427 | this.options.reverteffect(this.element, 428 | d[1]-this.delta[1], d[0]-this.delta[0]); 429 | } else { 430 | this.delta = d; 431 | } 432 | 433 | if(this.options.zindex) 434 | this.element.style.zIndex = this.originalZ; 435 | 436 | if(this.options.endeffect) 437 | this.options.endeffect(this.element); 438 | 439 | Draggables.deactivate(this); 440 | Droppables.reset(); 441 | }, 442 | 443 | keyPress: function(event) { 444 | if(event.keyCode!=Event.KEY_ESC) return; 445 | this.finishDrag(event, false); 446 | Event.stop(event); 447 | }, 448 | 449 | endDrag: function(event) { 450 | if(!this.dragging) return; 451 | this.stopScrolling(); 452 | this.finishDrag(event, true); 453 | Event.stop(event); 454 | }, 455 | 456 | draw: function(point) { 457 | var pos = Position.cumulativeOffset(this.element); 458 | if(this.options.ghosting) { 459 | var r = Position.realOffset(this.element); 460 | pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; 461 | } 462 | 463 | var d = this.currentDelta(); 464 | pos[0] -= d[0]; pos[1] -= d[1]; 465 | 466 | if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { 467 | pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; 468 | pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; 469 | } 470 | 471 | var p = [0,1].map(function(i){ 472 | return (point[i]-pos[i]-this.offset[i]) 473 | }.bind(this)); 474 | 475 | if(this.options.snap) { 476 | if(Object.isFunction(this.options.snap)) { 477 | p = this.options.snap(p[0],p[1],this); 478 | } else { 479 | if(Object.isArray(this.options.snap)) { 480 | p = p.map( function(v, i) { 481 | return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); 482 | } else { 483 | p = p.map( function(v) { 484 | return (v/this.options.snap).round()*this.options.snap }.bind(this)); 485 | } 486 | }} 487 | 488 | var style = this.element.style; 489 | if((!this.options.constraint) || (this.options.constraint=='horizontal')) 490 | style.left = p[0] + "px"; 491 | if((!this.options.constraint) || (this.options.constraint=='vertical')) 492 | style.top = p[1] + "px"; 493 | 494 | if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering 495 | }, 496 | 497 | stopScrolling: function() { 498 | if(this.scrollInterval) { 499 | clearInterval(this.scrollInterval); 500 | this.scrollInterval = null; 501 | Draggables._lastScrollPointer = null; 502 | } 503 | }, 504 | 505 | startScrolling: function(speed) { 506 | if(!(speed[0] || speed[1])) return; 507 | this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; 508 | this.lastScrolled = new Date(); 509 | this.scrollInterval = setInterval(this.scroll.bind(this), 10); 510 | }, 511 | 512 | scroll: function() { 513 | var current = new Date(); 514 | var delta = current - this.lastScrolled; 515 | this.lastScrolled = current; 516 | if(this.options.scroll == window) { 517 | with (this._getWindowScroll(this.options.scroll)) { 518 | if (this.scrollSpeed[0] || this.scrollSpeed[1]) { 519 | var d = delta / 1000; 520 | this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); 521 | } 522 | } 523 | } else { 524 | this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; 525 | this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; 526 | } 527 | 528 | Position.prepare(); 529 | Droppables.show(Draggables._lastPointer, this.element); 530 | Draggables.notify('onDrag', this); 531 | if (this._isScrollChild) { 532 | Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); 533 | Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; 534 | Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; 535 | if (Draggables._lastScrollPointer[0] < 0) 536 | Draggables._lastScrollPointer[0] = 0; 537 | if (Draggables._lastScrollPointer[1] < 0) 538 | Draggables._lastScrollPointer[1] = 0; 539 | this.draw(Draggables._lastScrollPointer); 540 | } 541 | 542 | if(this.options.change) this.options.change(this); 543 | }, 544 | 545 | _getWindowScroll: function(w) { 546 | var T, L, W, H; 547 | with (w.document) { 548 | if (w.document.documentElement && documentElement.scrollTop) { 549 | T = documentElement.scrollTop; 550 | L = documentElement.scrollLeft; 551 | } else if (w.document.body) { 552 | T = body.scrollTop; 553 | L = body.scrollLeft; 554 | } 555 | if (w.innerWidth) { 556 | W = w.innerWidth; 557 | H = w.innerHeight; 558 | } else if (w.document.documentElement && documentElement.clientWidth) { 559 | W = documentElement.clientWidth; 560 | H = documentElement.clientHeight; 561 | } else { 562 | W = body.offsetWidth; 563 | H = body.offsetHeight; 564 | } 565 | } 566 | return { top: T, left: L, width: W, height: H }; 567 | } 568 | }); 569 | 570 | Draggable._dragging = { }; 571 | 572 | /*--------------------------------------------------------------------------*/ 573 | 574 | var SortableObserver = Class.create({ 575 | initialize: function(element, observer) { 576 | this.element = $(element); 577 | this.observer = observer; 578 | this.lastValue = Sortable.serialize(this.element); 579 | }, 580 | 581 | onStart: function() { 582 | this.lastValue = Sortable.serialize(this.element); 583 | }, 584 | 585 | onEnd: function() { 586 | Sortable.unmark(); 587 | if(this.lastValue != Sortable.serialize(this.element)) 588 | this.observer(this.element) 589 | } 590 | }); 591 | 592 | var Sortable = { 593 | SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, 594 | 595 | sortables: { }, 596 | 597 | _findRootElement: function(element) { 598 | while (element.tagName.toUpperCase() != "BODY") { 599 | if(element.id && Sortable.sortables[element.id]) return element; 600 | element = element.parentNode; 601 | } 602 | }, 603 | 604 | options: function(element) { 605 | element = Sortable._findRootElement($(element)); 606 | if(!element) return; 607 | return Sortable.sortables[element.id]; 608 | }, 609 | 610 | destroy: function(element){ 611 | element = $(element); 612 | var s = Sortable.sortables[element.id]; 613 | 614 | if(s) { 615 | Draggables.removeObserver(s.element); 616 | s.droppables.each(function(d){ Droppables.remove(d) }); 617 | s.draggables.invoke('destroy'); 618 | 619 | delete Sortable.sortables[s.element.id]; 620 | } 621 | }, 622 | 623 | create: function(element) { 624 | element = $(element); 625 | var options = Object.extend({ 626 | element: element, 627 | tag: 'li', // assumes li children, override with tag: 'tagname' 628 | dropOnEmpty: false, 629 | tree: false, 630 | treeTag: 'ul', 631 | overlap: 'vertical', // one of 'vertical', 'horizontal' 632 | constraint: 'vertical', // one of 'vertical', 'horizontal', false 633 | containment: element, // also takes array of elements (or id's); or false 634 | handle: false, // or a CSS class 635 | only: false, 636 | delay: 0, 637 | hoverclass: null, 638 | ghosting: false, 639 | quiet: false, 640 | scroll: false, 641 | scrollSensitivity: 20, 642 | scrollSpeed: 15, 643 | format: this.SERIALIZE_RULE, 644 | 645 | // these take arrays of elements or ids and can be 646 | // used for better initialization performance 647 | elements: false, 648 | handles: false, 649 | 650 | onChange: Prototype.emptyFunction, 651 | onUpdate: Prototype.emptyFunction 652 | }, arguments[1] || { }); 653 | 654 | // clear any old sortable with same element 655 | this.destroy(element); 656 | 657 | // build options for the draggables 658 | var options_for_draggable = { 659 | revert: true, 660 | quiet: options.quiet, 661 | scroll: options.scroll, 662 | scrollSpeed: options.scrollSpeed, 663 | scrollSensitivity: options.scrollSensitivity, 664 | delay: options.delay, 665 | ghosting: options.ghosting, 666 | constraint: options.constraint, 667 | handle: options.handle }; 668 | 669 | if(options.starteffect) 670 | options_for_draggable.starteffect = options.starteffect; 671 | 672 | if(options.reverteffect) 673 | options_for_draggable.reverteffect = options.reverteffect; 674 | else 675 | if(options.ghosting) options_for_draggable.reverteffect = function(element) { 676 | element.style.top = 0; 677 | element.style.left = 0; 678 | }; 679 | 680 | if(options.endeffect) 681 | options_for_draggable.endeffect = options.endeffect; 682 | 683 | if(options.zindex) 684 | options_for_draggable.zindex = options.zindex; 685 | 686 | // build options for the droppables 687 | var options_for_droppable = { 688 | overlap: options.overlap, 689 | containment: options.containment, 690 | tree: options.tree, 691 | hoverclass: options.hoverclass, 692 | onHover: Sortable.onHover 693 | }; 694 | 695 | var options_for_tree = { 696 | onHover: Sortable.onEmptyHover, 697 | overlap: options.overlap, 698 | containment: options.containment, 699 | hoverclass: options.hoverclass 700 | }; 701 | 702 | // fix for gecko engine 703 | Element.cleanWhitespace(element); 704 | 705 | options.draggables = []; 706 | options.droppables = []; 707 | 708 | // drop on empty handling 709 | if(options.dropOnEmpty || options.tree) { 710 | Droppables.add(element, options_for_tree); 711 | options.droppables.push(element); 712 | } 713 | 714 | (options.elements || this.findElements(element, options) || []).each( function(e,i) { 715 | var handle = options.handles ? $(options.handles[i]) : 716 | (options.handle ? $(e).select('.' + options.handle)[0] : e); 717 | options.draggables.push( 718 | new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); 719 | Droppables.add(e, options_for_droppable); 720 | if(options.tree) e.treeNode = element; 721 | options.droppables.push(e); 722 | }); 723 | 724 | if(options.tree) { 725 | (Sortable.findTreeElements(element, options) || []).each( function(e) { 726 | Droppables.add(e, options_for_tree); 727 | e.treeNode = element; 728 | options.droppables.push(e); 729 | }); 730 | } 731 | 732 | // keep reference 733 | this.sortables[element.id] = options; 734 | 735 | // for onupdate 736 | Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 737 | 738 | }, 739 | 740 | // return all suitable-for-sortable elements in a guaranteed order 741 | findElements: function(element, options) { 742 | return Element.findChildren( 743 | element, options.only, options.tree ? true : false, options.tag); 744 | }, 745 | 746 | findTreeElements: function(element, options) { 747 | return Element.findChildren( 748 | element, options.only, options.tree ? true : false, options.treeTag); 749 | }, 750 | 751 | onHover: function(element, dropon, overlap) { 752 | if(Element.isParent(dropon, element)) return; 753 | 754 | if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { 755 | return; 756 | } else if(overlap>0.5) { 757 | Sortable.mark(dropon, 'before'); 758 | if(dropon.previousSibling != element) { 759 | var oldParentNode = element.parentNode; 760 | element.style.visibility = "hidden"; // fix gecko rendering 761 | dropon.parentNode.insertBefore(element, dropon); 762 | if(dropon.parentNode!=oldParentNode) 763 | Sortable.options(oldParentNode).onChange(element); 764 | Sortable.options(dropon.parentNode).onChange(element); 765 | } 766 | } else { 767 | Sortable.mark(dropon, 'after'); 768 | var nextElement = dropon.nextSibling || null; 769 | if(nextElement != element) { 770 | var oldParentNode = element.parentNode; 771 | element.style.visibility = "hidden"; // fix gecko rendering 772 | dropon.parentNode.insertBefore(element, nextElement); 773 | if(dropon.parentNode!=oldParentNode) 774 | Sortable.options(oldParentNode).onChange(element); 775 | Sortable.options(dropon.parentNode).onChange(element); 776 | } 777 | } 778 | }, 779 | 780 | onEmptyHover: function(element, dropon, overlap) { 781 | var oldParentNode = element.parentNode; 782 | var droponOptions = Sortable.options(dropon); 783 | 784 | if(!Element.isParent(dropon, element)) { 785 | var index; 786 | 787 | var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); 788 | var child = null; 789 | 790 | if(children) { 791 | var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); 792 | 793 | for (index = 0; index < children.length; index += 1) { 794 | if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { 795 | offset -= Element.offsetSize (children[index], droponOptions.overlap); 796 | } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { 797 | child = index + 1 < children.length ? children[index + 1] : null; 798 | break; 799 | } else { 800 | child = children[index]; 801 | break; 802 | } 803 | } 804 | } 805 | 806 | dropon.insertBefore(element, child); 807 | 808 | Sortable.options(oldParentNode).onChange(element); 809 | droponOptions.onChange(element); 810 | } 811 | }, 812 | 813 | unmark: function() { 814 | if(Sortable._marker) Sortable._marker.hide(); 815 | }, 816 | 817 | mark: function(dropon, position) { 818 | // mark on ghosting only 819 | var sortable = Sortable.options(dropon.parentNode); 820 | if(sortable && !sortable.ghosting) return; 821 | 822 | if(!Sortable._marker) { 823 | Sortable._marker = 824 | ($('dropmarker') || Element.extend(document.createElement('DIV'))). 825 | hide().addClassName('dropmarker').setStyle({position:'absolute'}); 826 | document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); 827 | } 828 | var offsets = Position.cumulativeOffset(dropon); 829 | Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); 830 | 831 | if(position=='after') 832 | if(sortable.overlap == 'horizontal') 833 | Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); 834 | else 835 | Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); 836 | 837 | Sortable._marker.show(); 838 | }, 839 | 840 | _tree: function(element, options, parent) { 841 | var children = Sortable.findElements(element, options) || []; 842 | 843 | for (var i = 0; i < children.length; ++i) { 844 | var match = children[i].id.match(options.format); 845 | 846 | if (!match) continue; 847 | 848 | var child = { 849 | id: encodeURIComponent(match ? match[1] : null), 850 | element: element, 851 | parent: parent, 852 | children: [], 853 | position: parent.children.length, 854 | container: $(children[i]).down(options.treeTag) 855 | }; 856 | 857 | /* Get the element containing the children and recurse over it */ 858 | if (child.container) 859 | this._tree(child.container, options, child); 860 | 861 | parent.children.push (child); 862 | } 863 | 864 | return parent; 865 | }, 866 | 867 | tree: function(element) { 868 | element = $(element); 869 | var sortableOptions = this.options(element); 870 | var options = Object.extend({ 871 | tag: sortableOptions.tag, 872 | treeTag: sortableOptions.treeTag, 873 | only: sortableOptions.only, 874 | name: element.id, 875 | format: sortableOptions.format 876 | }, arguments[1] || { }); 877 | 878 | var root = { 879 | id: null, 880 | parent: null, 881 | children: [], 882 | container: element, 883 | position: 0 884 | }; 885 | 886 | return Sortable._tree(element, options, root); 887 | }, 888 | 889 | /* Construct a [i] index for a particular node */ 890 | _constructIndex: function(node) { 891 | var index = ''; 892 | do { 893 | if (node.id) index = '[' + node.position + ']' + index; 894 | } while ((node = node.parent) != null); 895 | return index; 896 | }, 897 | 898 | sequence: function(element) { 899 | element = $(element); 900 | var options = Object.extend(this.options(element), arguments[1] || { }); 901 | 902 | return $(this.findElements(element, options) || []).map( function(item) { 903 | return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; 904 | }); 905 | }, 906 | 907 | setSequence: function(element, new_sequence) { 908 | element = $(element); 909 | var options = Object.extend(this.options(element), arguments[2] || { }); 910 | 911 | var nodeMap = { }; 912 | this.findElements(element, options).each( function(n) { 913 | if (n.id.match(options.format)) 914 | nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; 915 | n.parentNode.removeChild(n); 916 | }); 917 | 918 | new_sequence.each(function(ident) { 919 | var n = nodeMap[ident]; 920 | if (n) { 921 | n[1].appendChild(n[0]); 922 | delete nodeMap[ident]; 923 | } 924 | }); 925 | }, 926 | 927 | serialize: function(element) { 928 | element = $(element); 929 | var options = Object.extend(Sortable.options(element), arguments[1] || { }); 930 | var name = encodeURIComponent( 931 | (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); 932 | 933 | if (options.tree) { 934 | return Sortable.tree(element, arguments[1]).children.map( function (item) { 935 | return [name + Sortable._constructIndex(item) + "[id]=" + 936 | encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); 937 | }).flatten().join('&'); 938 | } else { 939 | return Sortable.sequence(element, arguments[1]).map( function(item) { 940 | return name + "[]=" + encodeURIComponent(item); 941 | }).join('&'); 942 | } 943 | } 944 | }; 945 | 946 | // Returns true if child is contained within element 947 | Element.isParent = function(child, element) { 948 | if (!child.parentNode || child == element) return false; 949 | if (child.parentNode == element) return true; 950 | return Element.isParent(child.parentNode, element); 951 | }; 952 | 953 | Element.findChildren = function(element, only, recursive, tagName) { 954 | if(!element.hasChildNodes()) return null; 955 | tagName = tagName.toUpperCase(); 956 | if(only) only = [only].flatten(); 957 | var elements = []; 958 | $A(element.childNodes).each( function(e) { 959 | if(e.tagName && e.tagName.toUpperCase()==tagName && 960 | (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) 961 | elements.push(e); 962 | if(recursive) { 963 | var grandchildren = Element.findChildren(e, only, recursive, tagName); 964 | if(grandchildren) elements.push(grandchildren); 965 | } 966 | }); 967 | 968 | return (elements.length>0 ? elements.flatten() : []); 969 | }; 970 | 971 | Element.offsetSize = function (element, type) { 972 | return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; 973 | }; -------------------------------------------------------------------------------- /test/rails/2/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /test/rails/2/script/about: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | $LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info" 4 | require 'commands/about' 5 | -------------------------------------------------------------------------------- /test/rails/2/script/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/console' 4 | -------------------------------------------------------------------------------- /test/rails/2/script/dbconsole: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/dbconsole' 4 | -------------------------------------------------------------------------------- /test/rails/2/script/destroy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/destroy' 4 | -------------------------------------------------------------------------------- /test/rails/2/script/generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/generate' 4 | -------------------------------------------------------------------------------- /test/rails/2/script/performance/benchmarker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../../config/boot', __FILE__) 3 | require 'commands/performance/benchmarker' 4 | -------------------------------------------------------------------------------- /test/rails/2/script/performance/profiler: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../../config/boot', __FILE__) 3 | require 'commands/performance/profiler' 4 | -------------------------------------------------------------------------------- /test/rails/2/script/plugin: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/plugin' 4 | -------------------------------------------------------------------------------- /test/rails/2/script/runner: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/runner' 4 | -------------------------------------------------------------------------------- /test/rails/2/script/server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require File.expand_path('../../config/boot', __FILE__) 3 | require 'commands/server' 4 | -------------------------------------------------------------------------------- /test/rails/2/test: -------------------------------------------------------------------------------- 1 | ../common/test -------------------------------------------------------------------------------- /test/rails/3/Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '3.0.0' 4 | 5 | # Bundle edge Rails instead: 6 | # gem 'rails', :git => 'git://github.com/rails/rails.git' 7 | 8 | gem 'sqlite3-ruby', :require => 'sqlite3' 9 | 10 | # Use unicorn as the web server 11 | # gem 'unicorn' 12 | gem "central_logger", :require => "central_logger", :path => "../../.." 13 | 14 | # Deploy with Capistrano 15 | # gem 'capistrano' 16 | 17 | # To use debugger 18 | # gem 'ruby-debug' 19 | 20 | # Bundle the extra gems: 21 | # gem 'bj' 22 | # gem 'nokogiri' 23 | # gem 'sqlite3-ruby', :require => 'sqlite3' 24 | # gem 'aws-s3', :require => 'aws/s3' 25 | 26 | # Bundle gems for the local environment. Make sure to 27 | # put test-only gems in this group so their generators 28 | # and rake tasks are available in development mode: 29 | # group :development, :test do 30 | # gem 'webrat' 31 | # end 32 | -------------------------------------------------------------------------------- /test/rails/3/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../../.. 3 | specs: 4 | central_logger (0.3.1) 5 | bson_ext 6 | bundler 7 | mongo 8 | rake 9 | 10 | GEM 11 | remote: http://rubygems.org/ 12 | specs: 13 | abstract (1.0.0) 14 | actionmailer (3.0.0) 15 | actionpack (= 3.0.0) 16 | mail (~> 2.2.5) 17 | actionpack (3.0.0) 18 | activemodel (= 3.0.0) 19 | activesupport (= 3.0.0) 20 | builder (~> 2.1.2) 21 | erubis (~> 2.6.6) 22 | i18n (~> 0.4.1) 23 | rack (~> 1.2.1) 24 | rack-mount (~> 0.6.12) 25 | rack-test (~> 0.5.4) 26 | tzinfo (~> 0.3.23) 27 | activemodel (3.0.0) 28 | activesupport (= 3.0.0) 29 | builder (~> 2.1.2) 30 | i18n (~> 0.4.1) 31 | activerecord (3.0.0) 32 | activemodel (= 3.0.0) 33 | activesupport (= 3.0.0) 34 | arel (~> 1.0.0) 35 | tzinfo (~> 0.3.23) 36 | activeresource (3.0.0) 37 | activemodel (= 3.0.0) 38 | activesupport (= 3.0.0) 39 | activesupport (3.0.0) 40 | arel (1.0.1) 41 | activesupport (~> 3.0.0) 42 | bson (1.2.4) 43 | bson_ext (1.2.4) 44 | builder (2.1.2) 45 | erubis (2.6.6) 46 | abstract (>= 1.0.0) 47 | i18n (0.4.2) 48 | mail (2.2.9) 49 | activesupport (>= 2.3.6) 50 | i18n (~> 0.4.1) 51 | mime-types (~> 1.16) 52 | treetop (~> 1.4.8) 53 | mime-types (1.16) 54 | mongo (1.2.4) 55 | bson (>= 1.2.4) 56 | polyglot (0.3.1) 57 | rack (1.2.1) 58 | rack-mount (0.6.13) 59 | rack (>= 1.0.0) 60 | rack-test (0.5.6) 61 | rack (>= 1.0) 62 | rails (3.0.0) 63 | actionmailer (= 3.0.0) 64 | actionpack (= 3.0.0) 65 | activerecord (= 3.0.0) 66 | activeresource (= 3.0.0) 67 | activesupport (= 3.0.0) 68 | bundler (~> 1.0.0) 69 | railties (= 3.0.0) 70 | railties (3.0.0) 71 | actionpack (= 3.0.0) 72 | activesupport (= 3.0.0) 73 | rake (>= 0.8.4) 74 | thor (~> 0.14.0) 75 | rake (0.8.7) 76 | sqlite3-ruby (1.3.2) 77 | thor (0.14.3) 78 | treetop (1.4.8) 79 | polyglot (>= 0.3.1) 80 | tzinfo (0.3.23) 81 | 82 | PLATFORMS 83 | ruby 84 | 85 | DEPENDENCIES 86 | central_logger! 87 | rails (= 3.0.0) 88 | sqlite3-ruby 89 | -------------------------------------------------------------------------------- /test/rails/3/README: -------------------------------------------------------------------------------- 1 | == Welcome to Rails 2 | 3 | Rails is a web-application framework that includes everything needed to create 4 | database-backed web applications according to the Model-View-Control pattern. 5 | 6 | This pattern splits the view (also called the presentation) into "dumb" 7 | templates that are primarily responsible for inserting pre-built data in between 8 | HTML tags. The model contains the "smart" domain objects (such as Account, 9 | Product, Person, Post) that holds all the business logic and knows how to 10 | persist themselves to a database. The controller handles the incoming requests 11 | (such as Save New Account, Update Product, Show Post) by manipulating the model 12 | and directing data to the view. 13 | 14 | In Rails, the model is handled by what's called an object-relational mapping 15 | layer entitled Active Record. This layer allows you to present the data from 16 | database rows as objects and embellish these data objects with business logic 17 | methods. You can read more about Active Record in 18 | link:files/vendor/rails/activerecord/README.html. 19 | 20 | The controller and view are handled by the Action Pack, which handles both 21 | layers by its two parts: Action View and Action Controller. These two layers 22 | are bundled in a single package due to their heavy interdependence. This is 23 | unlike the relationship between the Active Record and Action Pack that is much 24 | more separate. Each of these packages can be used independently outside of 25 | Rails. You can read more about Action Pack in 26 | link:files/vendor/rails/actionpack/README.html. 27 | 28 | 29 | == Getting Started 30 | 31 | 1. At the command prompt, create a new Rails application: 32 | rails new myapp (where myapp is the application name) 33 | 34 | 2. Change directory to myapp and start the web server: 35 | cd myapp; rails server (run with --help for options) 36 | 37 | 3. Go to http://localhost:3000/ and you'll see: 38 | "Welcome aboard: You're riding Ruby on Rails!" 39 | 40 | 4. Follow the guidelines to start developing your application. You can find 41 | the following resources handy: 42 | 43 | * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html 44 | * Ruby on Rails Tutorial Book: http://www.railstutorial.org/ 45 | 46 | 47 | == Debugging Rails 48 | 49 | Sometimes your application goes wrong. Fortunately there are a lot of tools that 50 | will help you debug it and get it back on the rails. 51 | 52 | First area to check is the application log files. Have "tail -f" commands 53 | running on the server.log and development.log. Rails will automatically display 54 | debugging and runtime information to these files. Debugging info will also be 55 | shown in the browser on requests from 127.0.0.1. 56 | 57 | You can also log your own messages directly into the log file from your code 58 | using the Ruby logger class from inside your controllers. Example: 59 | 60 | class WeblogController < ActionController::Base 61 | def destroy 62 | @weblog = Weblog.find(params[:id]) 63 | @weblog.destroy 64 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") 65 | end 66 | end 67 | 68 | The result will be a message in your log file along the lines of: 69 | 70 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! 71 | 72 | More information on how to use the logger is at http://www.ruby-doc.org/core/ 73 | 74 | Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are 75 | several books available online as well: 76 | 77 | * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) 78 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) 79 | 80 | These two books will bring you up to speed on the Ruby language and also on 81 | programming in general. 82 | 83 | 84 | == Debugger 85 | 86 | Debugger support is available through the debugger command when you start your 87 | Mongrel or WEBrick server with --debugger. This means that you can break out of 88 | execution at any point in the code, investigate and change the model, and then, 89 | resume execution! You need to install ruby-debug to run the server in debugging 90 | mode. With gems, use sudo gem install ruby-debug. Example: 91 | 92 | class WeblogController < ActionController::Base 93 | def index 94 | @posts = Post.find(:all) 95 | debugger 96 | end 97 | end 98 | 99 | So the controller will accept the action, run the first line, then present you 100 | with a IRB prompt in the server window. Here you can do things like: 101 | 102 | >> @posts.inspect 103 | => "[#nil, "body"=>nil, "id"=>"1"}>, 105 | #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" 107 | >> @posts.first.title = "hello from a debugger" 108 | => "hello from a debugger" 109 | 110 | ...and even better, you can examine how your runtime objects actually work: 111 | 112 | >> f = @posts.first 113 | => #nil, "body"=>nil, "id"=>"1"}> 114 | >> f. 115 | Display all 152 possibilities? (y or n) 116 | 117 | Finally, when you're ready to resume execution, you can enter "cont". 118 | 119 | 120 | == Console 121 | 122 | The console is a Ruby shell, which allows you to interact with your 123 | application's domain model. Here you'll have all parts of the application 124 | configured, just like it is when the application is running. You can inspect 125 | domain models, change values, and save to the database. Starting the script 126 | without arguments will launch it in the development environment. 127 | 128 | To start the console, run rails console from the application 129 | directory. 130 | 131 | Options: 132 | 133 | * Passing the -s, --sandbox argument will rollback any modifications 134 | made to the database. 135 | * Passing an environment name as an argument will load the corresponding 136 | environment. Example: rails console production. 137 | 138 | To reload your controllers and models after launching the console run 139 | reload! 140 | 141 | More information about irb can be found at: 142 | link:http://www.rubycentral.com/pickaxe/irb.html 143 | 144 | 145 | == dbconsole 146 | 147 | You can go to the command line of your database directly through rails 148 | dbconsole. You would be connected to the database with the credentials 149 | defined in database.yml. Starting the script without arguments will connect you 150 | to the development database. Passing an argument will connect you to a different 151 | database, like rails dbconsole production. Currently works for MySQL, 152 | PostgreSQL and SQLite 3. 153 | 154 | == Description of Contents 155 | 156 | The default directory structure of a generated Ruby on Rails application: 157 | 158 | |-- app 159 | | |-- controllers 160 | | |-- helpers 161 | | |-- models 162 | | `-- views 163 | | `-- layouts 164 | |-- config 165 | | |-- environments 166 | | |-- initializers 167 | | `-- locales 168 | |-- db 169 | |-- doc 170 | |-- lib 171 | | `-- tasks 172 | |-- log 173 | |-- public 174 | | |-- images 175 | | |-- javascripts 176 | | `-- stylesheets 177 | |-- script 178 | | `-- performance 179 | |-- test 180 | | |-- fixtures 181 | | |-- functional 182 | | |-- integration 183 | | |-- performance 184 | | `-- unit 185 | |-- tmp 186 | | |-- cache 187 | | |-- pids 188 | | |-- sessions 189 | | `-- sockets 190 | `-- vendor 191 | `-- plugins 192 | 193 | app 194 | Holds all the code that's specific to this particular application. 195 | 196 | app/controllers 197 | Holds controllers that should be named like weblogs_controller.rb for 198 | automated URL mapping. All controllers should descend from 199 | ApplicationController which itself descends from ActionController::Base. 200 | 201 | app/models 202 | Holds models that should be named like post.rb. Models descend from 203 | ActiveRecord::Base by default. 204 | 205 | app/views 206 | Holds the template files for the view that should be named like 207 | weblogs/index.html.erb for the WeblogsController#index action. All views use 208 | eRuby syntax by default. 209 | 210 | app/views/layouts 211 | Holds the template files for layouts to be used with views. This models the 212 | common header/footer method of wrapping views. In your views, define a layout 213 | using the layout :default and create a file named default.html.erb. 214 | Inside default.html.erb, call <% yield %> to render the view using this 215 | layout. 216 | 217 | app/helpers 218 | Holds view helpers that should be named like weblogs_helper.rb. These are 219 | generated for you automatically when using generators for controllers. 220 | Helpers can be used to wrap functionality for your views into methods. 221 | 222 | config 223 | Configuration files for the Rails environment, the routing map, the database, 224 | and other dependencies. 225 | 226 | db 227 | Contains the database schema in schema.rb. db/migrate contains all the 228 | sequence of Migrations for your schema. 229 | 230 | doc 231 | This directory is where your application documentation will be stored when 232 | generated using rake doc:app 233 | 234 | lib 235 | Application specific libraries. Basically, any kind of custom code that 236 | doesn't belong under controllers, models, or helpers. This directory is in 237 | the load path. 238 | 239 | public 240 | The directory available for the web server. Contains subdirectories for 241 | images, stylesheets, and javascripts. Also contains the dispatchers and the 242 | default HTML files. This should be set as the DOCUMENT_ROOT of your web 243 | server. 244 | 245 | script 246 | Helper scripts for automation and generation. 247 | 248 | test 249 | Unit and functional tests along with fixtures. When using the rails generate 250 | command, template test files will be generated for you and placed in this 251 | directory. 252 | 253 | vendor 254 | External libraries that the application depends on. Also includes the plugins 255 | subdirectory. If the app has frozen rails, those gems also go here, under 256 | vendor/rails/. This directory is in the load path. 257 | -------------------------------------------------------------------------------- /test/rails/3/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | require 'rake' 6 | 7 | LoggerAndRails3::Application.load_tasks 8 | -------------------------------------------------------------------------------- /test/rails/3/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include CentralLogger::Filter 3 | protect_from_forgery 4 | end 5 | -------------------------------------------------------------------------------- /test/rails/3/app/controllers/order_controller.rb: -------------------------------------------------------------------------------- 1 | ../../../common/app/controllers/order_controller.rb -------------------------------------------------------------------------------- /test/rails/3/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/rails/3/app/helpers/order_helper.rb: -------------------------------------------------------------------------------- 1 | module OrderHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/rails/3/app/views: -------------------------------------------------------------------------------- 1 | ../../common/app/views -------------------------------------------------------------------------------- /test/rails/3/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run LoggerAndRails3::Application 5 | -------------------------------------------------------------------------------- /test/rails/3/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # If you have a Gemfile, require the gems listed there, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(:default, Rails.env) if defined?(Bundler) 8 | 9 | module LoggerAndRails3 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Custom directories with classes and modules you want to be autoloadable. 16 | # config.autoload_paths += %W(#{config.root}/extras) 17 | 18 | # Only load the plugins named here, in the order given (default is alphabetical). 19 | # :all can be used as a placeholder for all plugins not explicitly named. 20 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 21 | 22 | # Activate observers that should always be running. 23 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 24 | 25 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 26 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 27 | # config.time_zone = 'Central Time (US & Canada)' 28 | 29 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 30 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 31 | # config.i18n.default_locale = :de 32 | 33 | # JavaScript files you want as :defaults (application.js is always included). 34 | # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) 35 | 36 | # Configure the default encoding used in templates for Ruby 1.9. 37 | config.encoding = "utf-8" 38 | 39 | # Configure sensitive parameters which will be filtered from the log file. 40 | config.filter_parameters += [:password] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/rails/3/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | gemfile = File.expand_path('../../Gemfile', __FILE__) 5 | begin 6 | ENV['BUNDLE_GEMFILE'] = gemfile 7 | require 'bundler' 8 | Bundler.setup 9 | rescue Bundler::GemNotFound => e 10 | STDERR.puts e.message 11 | STDERR.puts "Try running `bundle install`." 12 | exit! 13 | end if File.exist?(gemfile) 14 | -------------------------------------------------------------------------------- /test/rails/3/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3-ruby (not necessary on OS X Leopard) 3 | development: 4 | database: dev_db 5 | adapter: sqlite3 6 | mongo: 7 | database: logger_and_rails # required 8 | capsize: <%= 10.megabytes %> # default: 250MB for production; 100MB otherwise 9 | host: localhost # default: localhost 10 | port: 27017 # default: 27017 11 | 12 | # adapter: sqlite3 13 | # database: db/development.sqlite3 14 | # pool: 5 15 | # timeout: 5000 16 | 17 | # Warning: The database defined as "test" will be erased and 18 | # re-generated from your development database when you run "rake". 19 | # Do not set this db to the same as development or production. 20 | test: 21 | adapter: sqlite3 22 | database: test_db 23 | mongo: 24 | database: logger_and_rails # required 25 | capsize: <%= 10.megabytes %> # default: 250MB for production; 100MB otherwise 26 | host: localhost # default: localhost 27 | port: 27017 # default: 27017 28 | -------------------------------------------------------------------------------- /test/rails/3/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | LoggerAndRails3::Application.initialize! 6 | -------------------------------------------------------------------------------- /test/rails/3/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | LoggerAndRails3::Application.configure do 2 | # Settings specified here will take precedence over those in config/environment.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the webserver when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_view.debug_rjs = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Don't care if the mailer can't send 18 | config.action_mailer.raise_delivery_errors = false 19 | 20 | # Print deprecation notices to the Rails logger 21 | config.active_support.deprecation = :log 22 | 23 | # Only use best-standards-support built into browsers 24 | config.action_dispatch.best_standards_support = :builtin 25 | end 26 | 27 | -------------------------------------------------------------------------------- /test/rails/3/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | LoggerAndRails3::Application.configure do 2 | # Settings specified here will take precedence over those in config/environment.rb 3 | 4 | # The production environment is meant for finished, "live" apps. 5 | # Code is not reloaded between requests 6 | config.cache_classes = true 7 | 8 | # Full error reports are disabled and caching is turned on 9 | config.consider_all_requests_local = false 10 | config.action_controller.perform_caching = true 11 | 12 | # Specifies the header that your server uses for sending files 13 | config.action_dispatch.x_sendfile_header = "X-Sendfile" 14 | 15 | # For nginx: 16 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' 17 | 18 | # If you have no front-end server that supports something like X-Sendfile, 19 | # just comment this out and Rails will serve the files 20 | 21 | # See everything in the log (default is :info) 22 | # config.log_level = :debug 23 | 24 | # Use a different logger for distributed setups 25 | # config.logger = SyslogLogger.new 26 | 27 | # Use a different cache store in production 28 | # config.cache_store = :mem_cache_store 29 | 30 | # Disable Rails's static asset server 31 | # In production, Apache or nginx will already do this 32 | config.serve_static_assets = false 33 | 34 | # Enable serving of images, stylesheets, and javascripts from an asset server 35 | # config.action_controller.asset_host = "http://assets.example.com" 36 | 37 | # Disable delivery errors, bad email addresses will be ignored 38 | # config.action_mailer.raise_delivery_errors = false 39 | 40 | # Enable threaded mode 41 | # config.threadsafe! 42 | 43 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 44 | # the I18n.default_locale when a translation can not be found) 45 | config.i18n.fallbacks = true 46 | 47 | # Send deprecation notices to registered listeners 48 | config.active_support.deprecation = :notify 49 | end 50 | -------------------------------------------------------------------------------- /test/rails/3/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | LoggerAndRails3::Application.configure do 2 | # Settings specified here will take precedence over those in config/environment.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Log error messages when you accidentally call methods on nil. 11 | config.whiny_nils = true 12 | 13 | # Show full error reports and disable caching 14 | config.consider_all_requests_local = true 15 | config.action_controller.perform_caching = false 16 | 17 | # Raise exceptions instead of rendering exception templates 18 | config.action_dispatch.show_exceptions = false 19 | 20 | # Disable request forgery protection in test environment 21 | config.action_controller.allow_forgery_protection = false 22 | 23 | # Tell Action Mailer not to deliver emails to the real world. 24 | # The :test delivery method accumulates sent emails in the 25 | # ActionMailer::Base.deliveries array. 26 | config.action_mailer.delivery_method = :test 27 | 28 | # Use SQL instead of Active Record's schema dumper when creating the test database. 29 | # This is necessary if your schema can't be completely dumped by the schema dumper, 30 | # like if you have constraints or database-specific column types 31 | # config.active_record.schema_format = :sql 32 | 33 | # Print deprecation notices to the stderr 34 | config.active_support.deprecation = :stderr 35 | end 36 | -------------------------------------------------------------------------------- /test/rails/3/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/rails/3/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /test/rails/3/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /test/rails/3/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | LoggerAndRails3::Application.config.secret_token = 'a89a36902066b9302a19262c32a2ef7b30b643a9e6576d32ecbeb8e9e3c5c40e711c24f2af0b86239ffb68cd8e18d832edcad7411b30bea989e42371c8225c33' 8 | -------------------------------------------------------------------------------- /test/rails/3/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | LoggerAndRails3::Application.config.session_store :cookie_store, :key => '_logger_and_rails3_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rake db:sessions:create") 8 | # LoggerAndRails3::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /test/rails/3/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /test/rails/3/config/routes.rb: -------------------------------------------------------------------------------- 1 | LoggerAndRails3::Application.routes.draw do 2 | #resources :order 3 | #match 'blow_up' => 'order#blow_up' 4 | 5 | # The priority is based upon order of creation: 6 | # first created -> highest priority. 7 | 8 | # Sample of regular route: 9 | # match 'products/:id' => 'catalog#view' 10 | # Keep in mind you can assign values other than :controller and :action 11 | 12 | # Sample of named route: 13 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 14 | # This route can be invoked with purchase_url(:id => product.id) 15 | 16 | # Sample resource route (maps HTTP verbs to controller actions automatically): 17 | # resources :products 18 | 19 | # Sample resource route with options: 20 | # resources :products do 21 | # member do 22 | # get 'short' 23 | # post 'toggle' 24 | # end 25 | # 26 | # collection do 27 | # get 'sold' 28 | # end 29 | # end 30 | 31 | # Sample resource route with sub-resources: 32 | # resources :products do 33 | # resources :comments, :sales 34 | # resource :seller 35 | # end 36 | 37 | # Sample resource route with more complex sub-resources 38 | # resources :products do 39 | # resources :comments 40 | # resources :sales do 41 | # get 'recent', :on => :collection 42 | # end 43 | # end 44 | 45 | # Sample resource route within a namespace: 46 | # namespace :admin do 47 | # # Directs /admin/products/* to Admin::ProductsController 48 | # # (app/controllers/admin/products_controller.rb) 49 | # resources :products 50 | # end 51 | 52 | # You can have the root of your site routed with "root" 53 | # just remember to delete public/index.html. 54 | # root :to => "welcome#index" 55 | 56 | # See how all your routes lay out with "rake routes" 57 | 58 | # This is a legacy wild controller route that's not recommended for RESTful applications. 59 | # Note: This route will make all actions in every controller accessible via GET requests. 60 | match ':controller(/:action(/:id(.:format)))' 61 | end 62 | -------------------------------------------------------------------------------- /test/rails/3/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended to check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(:version => 0) do 14 | 15 | end 16 | -------------------------------------------------------------------------------- /test/rails/3/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) 7 | # Mayor.create(:name => 'Daley', :city => cities.first) 8 | -------------------------------------------------------------------------------- /test/rails/3/doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /test/rails/3/lib/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/customink/central_logger/72bba43d1990a4ba38b126749f1feb77ea108a4a/test/rails/3/lib/tasks/.gitkeep -------------------------------------------------------------------------------- /test/rails/3/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/rails/3/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/rails/3/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /test/rails/3/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/customink/central_logger/72bba43d1990a4ba38b126749f1feb77ea108a4a/test/rails/3/public/favicon.ico -------------------------------------------------------------------------------- /test/rails/3/public/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/customink/central_logger/72bba43d1990a4ba38b126749f1feb77ea108a4a/test/rails/3/public/images/rails.png -------------------------------------------------------------------------------- /test/rails/3/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ruby on Rails: Welcome aboard 5 | 172 | 185 | 186 | 187 |
188 | 201 | 202 |
203 | 207 | 208 | 212 | 213 |
214 |

Getting started

215 |

Here’s how to get rolling:

216 | 217 |
    218 |
  1. 219 |

    Use rails generate to create your models and controllers

    220 |

    To see all available options, run it without parameters.

    221 |
  2. 222 | 223 |
  3. 224 |

    Set up a default route and remove or rename this file

    225 |

    Routes are set up in config/routes.rb.

    226 |
  4. 227 | 228 |
  5. 229 |

    Create your database

    230 |

    Run rake db:migrate to create your database. If you're not using SQLite (the default), edit config/database.yml with your username and password.

    231 |
  6. 232 |
233 |
234 |
235 | 236 | 237 |
238 | 239 | 240 | -------------------------------------------------------------------------------- /test/rails/3/public/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // Place your application-specific JavaScript functions and classes here 2 | // This file is automatically included by javascript_include_tag :defaults 3 | -------------------------------------------------------------------------------- /test/rails/3/public/javascripts/dragdrop.js: -------------------------------------------------------------------------------- 1 | // script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 2 | 3 | // Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 4 | // 5 | // script.aculo.us is freely distributable under the terms of an MIT-style license. 6 | // For details, see the script.aculo.us web site: http://script.aculo.us/ 7 | 8 | if(Object.isUndefined(Effect)) 9 | throw("dragdrop.js requires including script.aculo.us' effects.js library"); 10 | 11 | var Droppables = { 12 | drops: [], 13 | 14 | remove: function(element) { 15 | this.drops = this.drops.reject(function(d) { return d.element==$(element) }); 16 | }, 17 | 18 | add: function(element) { 19 | element = $(element); 20 | var options = Object.extend({ 21 | greedy: true, 22 | hoverclass: null, 23 | tree: false 24 | }, arguments[1] || { }); 25 | 26 | // cache containers 27 | if(options.containment) { 28 | options._containers = []; 29 | var containment = options.containment; 30 | if(Object.isArray(containment)) { 31 | containment.each( function(c) { options._containers.push($(c)) }); 32 | } else { 33 | options._containers.push($(containment)); 34 | } 35 | } 36 | 37 | if(options.accept) options.accept = [options.accept].flatten(); 38 | 39 | Element.makePositioned(element); // fix IE 40 | options.element = element; 41 | 42 | this.drops.push(options); 43 | }, 44 | 45 | findDeepestChild: function(drops) { 46 | deepest = drops[0]; 47 | 48 | for (i = 1; i < drops.length; ++i) 49 | if (Element.isParent(drops[i].element, deepest.element)) 50 | deepest = drops[i]; 51 | 52 | return deepest; 53 | }, 54 | 55 | isContained: function(element, drop) { 56 | var containmentNode; 57 | if(drop.tree) { 58 | containmentNode = element.treeNode; 59 | } else { 60 | containmentNode = element.parentNode; 61 | } 62 | return drop._containers.detect(function(c) { return containmentNode == c }); 63 | }, 64 | 65 | isAffected: function(point, element, drop) { 66 | return ( 67 | (drop.element!=element) && 68 | ((!drop._containers) || 69 | this.isContained(element, drop)) && 70 | ((!drop.accept) || 71 | (Element.classNames(element).detect( 72 | function(v) { return drop.accept.include(v) } ) )) && 73 | Position.within(drop.element, point[0], point[1]) ); 74 | }, 75 | 76 | deactivate: function(drop) { 77 | if(drop.hoverclass) 78 | Element.removeClassName(drop.element, drop.hoverclass); 79 | this.last_active = null; 80 | }, 81 | 82 | activate: function(drop) { 83 | if(drop.hoverclass) 84 | Element.addClassName(drop.element, drop.hoverclass); 85 | this.last_active = drop; 86 | }, 87 | 88 | show: function(point, element) { 89 | if(!this.drops.length) return; 90 | var drop, affected = []; 91 | 92 | this.drops.each( function(drop) { 93 | if(Droppables.isAffected(point, element, drop)) 94 | affected.push(drop); 95 | }); 96 | 97 | if(affected.length>0) 98 | drop = Droppables.findDeepestChild(affected); 99 | 100 | if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); 101 | if (drop) { 102 | Position.within(drop.element, point[0], point[1]); 103 | if(drop.onHover) 104 | drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); 105 | 106 | if (drop != this.last_active) Droppables.activate(drop); 107 | } 108 | }, 109 | 110 | fire: function(event, element) { 111 | if(!this.last_active) return; 112 | Position.prepare(); 113 | 114 | if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) 115 | if (this.last_active.onDrop) { 116 | this.last_active.onDrop(element, this.last_active.element, event); 117 | return true; 118 | } 119 | }, 120 | 121 | reset: function() { 122 | if(this.last_active) 123 | this.deactivate(this.last_active); 124 | } 125 | }; 126 | 127 | var Draggables = { 128 | drags: [], 129 | observers: [], 130 | 131 | register: function(draggable) { 132 | if(this.drags.length == 0) { 133 | this.eventMouseUp = this.endDrag.bindAsEventListener(this); 134 | this.eventMouseMove = this.updateDrag.bindAsEventListener(this); 135 | this.eventKeypress = this.keyPress.bindAsEventListener(this); 136 | 137 | Event.observe(document, "mouseup", this.eventMouseUp); 138 | Event.observe(document, "mousemove", this.eventMouseMove); 139 | Event.observe(document, "keypress", this.eventKeypress); 140 | } 141 | this.drags.push(draggable); 142 | }, 143 | 144 | unregister: function(draggable) { 145 | this.drags = this.drags.reject(function(d) { return d==draggable }); 146 | if(this.drags.length == 0) { 147 | Event.stopObserving(document, "mouseup", this.eventMouseUp); 148 | Event.stopObserving(document, "mousemove", this.eventMouseMove); 149 | Event.stopObserving(document, "keypress", this.eventKeypress); 150 | } 151 | }, 152 | 153 | activate: function(draggable) { 154 | if(draggable.options.delay) { 155 | this._timeout = setTimeout(function() { 156 | Draggables._timeout = null; 157 | window.focus(); 158 | Draggables.activeDraggable = draggable; 159 | }.bind(this), draggable.options.delay); 160 | } else { 161 | window.focus(); // allows keypress events if window isn't currently focused, fails for Safari 162 | this.activeDraggable = draggable; 163 | } 164 | }, 165 | 166 | deactivate: function() { 167 | this.activeDraggable = null; 168 | }, 169 | 170 | updateDrag: function(event) { 171 | if(!this.activeDraggable) return; 172 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 173 | // Mozilla-based browsers fire successive mousemove events with 174 | // the same coordinates, prevent needless redrawing (moz bug?) 175 | if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; 176 | this._lastPointer = pointer; 177 | 178 | this.activeDraggable.updateDrag(event, pointer); 179 | }, 180 | 181 | endDrag: function(event) { 182 | if(this._timeout) { 183 | clearTimeout(this._timeout); 184 | this._timeout = null; 185 | } 186 | if(!this.activeDraggable) return; 187 | this._lastPointer = null; 188 | this.activeDraggable.endDrag(event); 189 | this.activeDraggable = null; 190 | }, 191 | 192 | keyPress: function(event) { 193 | if(this.activeDraggable) 194 | this.activeDraggable.keyPress(event); 195 | }, 196 | 197 | addObserver: function(observer) { 198 | this.observers.push(observer); 199 | this._cacheObserverCallbacks(); 200 | }, 201 | 202 | removeObserver: function(element) { // element instead of observer fixes mem leaks 203 | this.observers = this.observers.reject( function(o) { return o.element==element }); 204 | this._cacheObserverCallbacks(); 205 | }, 206 | 207 | notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' 208 | if(this[eventName+'Count'] > 0) 209 | this.observers.each( function(o) { 210 | if(o[eventName]) o[eventName](eventName, draggable, event); 211 | }); 212 | if(draggable.options[eventName]) draggable.options[eventName](draggable, event); 213 | }, 214 | 215 | _cacheObserverCallbacks: function() { 216 | ['onStart','onEnd','onDrag'].each( function(eventName) { 217 | Draggables[eventName+'Count'] = Draggables.observers.select( 218 | function(o) { return o[eventName]; } 219 | ).length; 220 | }); 221 | } 222 | }; 223 | 224 | /*--------------------------------------------------------------------------*/ 225 | 226 | var Draggable = Class.create({ 227 | initialize: function(element) { 228 | var defaults = { 229 | handle: false, 230 | reverteffect: function(element, top_offset, left_offset) { 231 | var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; 232 | new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, 233 | queue: {scope:'_draggable', position:'end'} 234 | }); 235 | }, 236 | endeffect: function(element) { 237 | var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; 238 | new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 239 | queue: {scope:'_draggable', position:'end'}, 240 | afterFinish: function(){ 241 | Draggable._dragging[element] = false 242 | } 243 | }); 244 | }, 245 | zindex: 1000, 246 | revert: false, 247 | quiet: false, 248 | scroll: false, 249 | scrollSensitivity: 20, 250 | scrollSpeed: 15, 251 | snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } 252 | delay: 0 253 | }; 254 | 255 | if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) 256 | Object.extend(defaults, { 257 | starteffect: function(element) { 258 | element._opacity = Element.getOpacity(element); 259 | Draggable._dragging[element] = true; 260 | new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 261 | } 262 | }); 263 | 264 | var options = Object.extend(defaults, arguments[1] || { }); 265 | 266 | this.element = $(element); 267 | 268 | if(options.handle && Object.isString(options.handle)) 269 | this.handle = this.element.down('.'+options.handle, 0); 270 | 271 | if(!this.handle) this.handle = $(options.handle); 272 | if(!this.handle) this.handle = this.element; 273 | 274 | if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { 275 | options.scroll = $(options.scroll); 276 | this._isScrollChild = Element.childOf(this.element, options.scroll); 277 | } 278 | 279 | Element.makePositioned(this.element); // fix IE 280 | 281 | this.options = options; 282 | this.dragging = false; 283 | 284 | this.eventMouseDown = this.initDrag.bindAsEventListener(this); 285 | Event.observe(this.handle, "mousedown", this.eventMouseDown); 286 | 287 | Draggables.register(this); 288 | }, 289 | 290 | destroy: function() { 291 | Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); 292 | Draggables.unregister(this); 293 | }, 294 | 295 | currentDelta: function() { 296 | return([ 297 | parseInt(Element.getStyle(this.element,'left') || '0'), 298 | parseInt(Element.getStyle(this.element,'top') || '0')]); 299 | }, 300 | 301 | initDrag: function(event) { 302 | if(!Object.isUndefined(Draggable._dragging[this.element]) && 303 | Draggable._dragging[this.element]) return; 304 | if(Event.isLeftClick(event)) { 305 | // abort on form elements, fixes a Firefox issue 306 | var src = Event.element(event); 307 | if((tag_name = src.tagName.toUpperCase()) && ( 308 | tag_name=='INPUT' || 309 | tag_name=='SELECT' || 310 | tag_name=='OPTION' || 311 | tag_name=='BUTTON' || 312 | tag_name=='TEXTAREA')) return; 313 | 314 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 315 | var pos = this.element.cumulativeOffset(); 316 | this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); 317 | 318 | Draggables.activate(this); 319 | Event.stop(event); 320 | } 321 | }, 322 | 323 | startDrag: function(event) { 324 | this.dragging = true; 325 | if(!this.delta) 326 | this.delta = this.currentDelta(); 327 | 328 | if(this.options.zindex) { 329 | this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); 330 | this.element.style.zIndex = this.options.zindex; 331 | } 332 | 333 | if(this.options.ghosting) { 334 | this._clone = this.element.cloneNode(true); 335 | this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); 336 | if (!this._originallyAbsolute) 337 | Position.absolutize(this.element); 338 | this.element.parentNode.insertBefore(this._clone, this.element); 339 | } 340 | 341 | if(this.options.scroll) { 342 | if (this.options.scroll == window) { 343 | var where = this._getWindowScroll(this.options.scroll); 344 | this.originalScrollLeft = where.left; 345 | this.originalScrollTop = where.top; 346 | } else { 347 | this.originalScrollLeft = this.options.scroll.scrollLeft; 348 | this.originalScrollTop = this.options.scroll.scrollTop; 349 | } 350 | } 351 | 352 | Draggables.notify('onStart', this, event); 353 | 354 | if(this.options.starteffect) this.options.starteffect(this.element); 355 | }, 356 | 357 | updateDrag: function(event, pointer) { 358 | if(!this.dragging) this.startDrag(event); 359 | 360 | if(!this.options.quiet){ 361 | Position.prepare(); 362 | Droppables.show(pointer, this.element); 363 | } 364 | 365 | Draggables.notify('onDrag', this, event); 366 | 367 | this.draw(pointer); 368 | if(this.options.change) this.options.change(this); 369 | 370 | if(this.options.scroll) { 371 | this.stopScrolling(); 372 | 373 | var p; 374 | if (this.options.scroll == window) { 375 | with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } 376 | } else { 377 | p = Position.page(this.options.scroll); 378 | p[0] += this.options.scroll.scrollLeft + Position.deltaX; 379 | p[1] += this.options.scroll.scrollTop + Position.deltaY; 380 | p.push(p[0]+this.options.scroll.offsetWidth); 381 | p.push(p[1]+this.options.scroll.offsetHeight); 382 | } 383 | var speed = [0,0]; 384 | if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); 385 | if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); 386 | if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); 387 | if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); 388 | this.startScrolling(speed); 389 | } 390 | 391 | // fix AppleWebKit rendering 392 | if(Prototype.Browser.WebKit) window.scrollBy(0,0); 393 | 394 | Event.stop(event); 395 | }, 396 | 397 | finishDrag: function(event, success) { 398 | this.dragging = false; 399 | 400 | if(this.options.quiet){ 401 | Position.prepare(); 402 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; 403 | Droppables.show(pointer, this.element); 404 | } 405 | 406 | if(this.options.ghosting) { 407 | if (!this._originallyAbsolute) 408 | Position.relativize(this.element); 409 | delete this._originallyAbsolute; 410 | Element.remove(this._clone); 411 | this._clone = null; 412 | } 413 | 414 | var dropped = false; 415 | if(success) { 416 | dropped = Droppables.fire(event, this.element); 417 | if (!dropped) dropped = false; 418 | } 419 | if(dropped && this.options.onDropped) this.options.onDropped(this.element); 420 | Draggables.notify('onEnd', this, event); 421 | 422 | var revert = this.options.revert; 423 | if(revert && Object.isFunction(revert)) revert = revert(this.element); 424 | 425 | var d = this.currentDelta(); 426 | if(revert && this.options.reverteffect) { 427 | if (dropped == 0 || revert != 'failure') 428 | this.options.reverteffect(this.element, 429 | d[1]-this.delta[1], d[0]-this.delta[0]); 430 | } else { 431 | this.delta = d; 432 | } 433 | 434 | if(this.options.zindex) 435 | this.element.style.zIndex = this.originalZ; 436 | 437 | if(this.options.endeffect) 438 | this.options.endeffect(this.element); 439 | 440 | Draggables.deactivate(this); 441 | Droppables.reset(); 442 | }, 443 | 444 | keyPress: function(event) { 445 | if(event.keyCode!=Event.KEY_ESC) return; 446 | this.finishDrag(event, false); 447 | Event.stop(event); 448 | }, 449 | 450 | endDrag: function(event) { 451 | if(!this.dragging) return; 452 | this.stopScrolling(); 453 | this.finishDrag(event, true); 454 | Event.stop(event); 455 | }, 456 | 457 | draw: function(point) { 458 | var pos = this.element.cumulativeOffset(); 459 | if(this.options.ghosting) { 460 | var r = Position.realOffset(this.element); 461 | pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; 462 | } 463 | 464 | var d = this.currentDelta(); 465 | pos[0] -= d[0]; pos[1] -= d[1]; 466 | 467 | if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { 468 | pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; 469 | pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; 470 | } 471 | 472 | var p = [0,1].map(function(i){ 473 | return (point[i]-pos[i]-this.offset[i]) 474 | }.bind(this)); 475 | 476 | if(this.options.snap) { 477 | if(Object.isFunction(this.options.snap)) { 478 | p = this.options.snap(p[0],p[1],this); 479 | } else { 480 | if(Object.isArray(this.options.snap)) { 481 | p = p.map( function(v, i) { 482 | return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); 483 | } else { 484 | p = p.map( function(v) { 485 | return (v/this.options.snap).round()*this.options.snap }.bind(this)); 486 | } 487 | }} 488 | 489 | var style = this.element.style; 490 | if((!this.options.constraint) || (this.options.constraint=='horizontal')) 491 | style.left = p[0] + "px"; 492 | if((!this.options.constraint) || (this.options.constraint=='vertical')) 493 | style.top = p[1] + "px"; 494 | 495 | if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering 496 | }, 497 | 498 | stopScrolling: function() { 499 | if(this.scrollInterval) { 500 | clearInterval(this.scrollInterval); 501 | this.scrollInterval = null; 502 | Draggables._lastScrollPointer = null; 503 | } 504 | }, 505 | 506 | startScrolling: function(speed) { 507 | if(!(speed[0] || speed[1])) return; 508 | this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; 509 | this.lastScrolled = new Date(); 510 | this.scrollInterval = setInterval(this.scroll.bind(this), 10); 511 | }, 512 | 513 | scroll: function() { 514 | var current = new Date(); 515 | var delta = current - this.lastScrolled; 516 | this.lastScrolled = current; 517 | if(this.options.scroll == window) { 518 | with (this._getWindowScroll(this.options.scroll)) { 519 | if (this.scrollSpeed[0] || this.scrollSpeed[1]) { 520 | var d = delta / 1000; 521 | this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); 522 | } 523 | } 524 | } else { 525 | this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; 526 | this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; 527 | } 528 | 529 | Position.prepare(); 530 | Droppables.show(Draggables._lastPointer, this.element); 531 | Draggables.notify('onDrag', this); 532 | if (this._isScrollChild) { 533 | Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); 534 | Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; 535 | Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; 536 | if (Draggables._lastScrollPointer[0] < 0) 537 | Draggables._lastScrollPointer[0] = 0; 538 | if (Draggables._lastScrollPointer[1] < 0) 539 | Draggables._lastScrollPointer[1] = 0; 540 | this.draw(Draggables._lastScrollPointer); 541 | } 542 | 543 | if(this.options.change) this.options.change(this); 544 | }, 545 | 546 | _getWindowScroll: function(w) { 547 | var T, L, W, H; 548 | with (w.document) { 549 | if (w.document.documentElement && documentElement.scrollTop) { 550 | T = documentElement.scrollTop; 551 | L = documentElement.scrollLeft; 552 | } else if (w.document.body) { 553 | T = body.scrollTop; 554 | L = body.scrollLeft; 555 | } 556 | if (w.innerWidth) { 557 | W = w.innerWidth; 558 | H = w.innerHeight; 559 | } else if (w.document.documentElement && documentElement.clientWidth) { 560 | W = documentElement.clientWidth; 561 | H = documentElement.clientHeight; 562 | } else { 563 | W = body.offsetWidth; 564 | H = body.offsetHeight; 565 | } 566 | } 567 | return { top: T, left: L, width: W, height: H }; 568 | } 569 | }); 570 | 571 | Draggable._dragging = { }; 572 | 573 | /*--------------------------------------------------------------------------*/ 574 | 575 | var SortableObserver = Class.create({ 576 | initialize: function(element, observer) { 577 | this.element = $(element); 578 | this.observer = observer; 579 | this.lastValue = Sortable.serialize(this.element); 580 | }, 581 | 582 | onStart: function() { 583 | this.lastValue = Sortable.serialize(this.element); 584 | }, 585 | 586 | onEnd: function() { 587 | Sortable.unmark(); 588 | if(this.lastValue != Sortable.serialize(this.element)) 589 | this.observer(this.element) 590 | } 591 | }); 592 | 593 | var Sortable = { 594 | SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, 595 | 596 | sortables: { }, 597 | 598 | _findRootElement: function(element) { 599 | while (element.tagName.toUpperCase() != "BODY") { 600 | if(element.id && Sortable.sortables[element.id]) return element; 601 | element = element.parentNode; 602 | } 603 | }, 604 | 605 | options: function(element) { 606 | element = Sortable._findRootElement($(element)); 607 | if(!element) return; 608 | return Sortable.sortables[element.id]; 609 | }, 610 | 611 | destroy: function(element){ 612 | element = $(element); 613 | var s = Sortable.sortables[element.id]; 614 | 615 | if(s) { 616 | Draggables.removeObserver(s.element); 617 | s.droppables.each(function(d){ Droppables.remove(d) }); 618 | s.draggables.invoke('destroy'); 619 | 620 | delete Sortable.sortables[s.element.id]; 621 | } 622 | }, 623 | 624 | create: function(element) { 625 | element = $(element); 626 | var options = Object.extend({ 627 | element: element, 628 | tag: 'li', // assumes li children, override with tag: 'tagname' 629 | dropOnEmpty: false, 630 | tree: false, 631 | treeTag: 'ul', 632 | overlap: 'vertical', // one of 'vertical', 'horizontal' 633 | constraint: 'vertical', // one of 'vertical', 'horizontal', false 634 | containment: element, // also takes array of elements (or id's); or false 635 | handle: false, // or a CSS class 636 | only: false, 637 | delay: 0, 638 | hoverclass: null, 639 | ghosting: false, 640 | quiet: false, 641 | scroll: false, 642 | scrollSensitivity: 20, 643 | scrollSpeed: 15, 644 | format: this.SERIALIZE_RULE, 645 | 646 | // these take arrays of elements or ids and can be 647 | // used for better initialization performance 648 | elements: false, 649 | handles: false, 650 | 651 | onChange: Prototype.emptyFunction, 652 | onUpdate: Prototype.emptyFunction 653 | }, arguments[1] || { }); 654 | 655 | // clear any old sortable with same element 656 | this.destroy(element); 657 | 658 | // build options for the draggables 659 | var options_for_draggable = { 660 | revert: true, 661 | quiet: options.quiet, 662 | scroll: options.scroll, 663 | scrollSpeed: options.scrollSpeed, 664 | scrollSensitivity: options.scrollSensitivity, 665 | delay: options.delay, 666 | ghosting: options.ghosting, 667 | constraint: options.constraint, 668 | handle: options.handle }; 669 | 670 | if(options.starteffect) 671 | options_for_draggable.starteffect = options.starteffect; 672 | 673 | if(options.reverteffect) 674 | options_for_draggable.reverteffect = options.reverteffect; 675 | else 676 | if(options.ghosting) options_for_draggable.reverteffect = function(element) { 677 | element.style.top = 0; 678 | element.style.left = 0; 679 | }; 680 | 681 | if(options.endeffect) 682 | options_for_draggable.endeffect = options.endeffect; 683 | 684 | if(options.zindex) 685 | options_for_draggable.zindex = options.zindex; 686 | 687 | // build options for the droppables 688 | var options_for_droppable = { 689 | overlap: options.overlap, 690 | containment: options.containment, 691 | tree: options.tree, 692 | hoverclass: options.hoverclass, 693 | onHover: Sortable.onHover 694 | }; 695 | 696 | var options_for_tree = { 697 | onHover: Sortable.onEmptyHover, 698 | overlap: options.overlap, 699 | containment: options.containment, 700 | hoverclass: options.hoverclass 701 | }; 702 | 703 | // fix for gecko engine 704 | Element.cleanWhitespace(element); 705 | 706 | options.draggables = []; 707 | options.droppables = []; 708 | 709 | // drop on empty handling 710 | if(options.dropOnEmpty || options.tree) { 711 | Droppables.add(element, options_for_tree); 712 | options.droppables.push(element); 713 | } 714 | 715 | (options.elements || this.findElements(element, options) || []).each( function(e,i) { 716 | var handle = options.handles ? $(options.handles[i]) : 717 | (options.handle ? $(e).select('.' + options.handle)[0] : e); 718 | options.draggables.push( 719 | new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); 720 | Droppables.add(e, options_for_droppable); 721 | if(options.tree) e.treeNode = element; 722 | options.droppables.push(e); 723 | }); 724 | 725 | if(options.tree) { 726 | (Sortable.findTreeElements(element, options) || []).each( function(e) { 727 | Droppables.add(e, options_for_tree); 728 | e.treeNode = element; 729 | options.droppables.push(e); 730 | }); 731 | } 732 | 733 | // keep reference 734 | this.sortables[element.identify()] = options; 735 | 736 | // for onupdate 737 | Draggables.addObserver(new SortableObserver(element, options.onUpdate)); 738 | 739 | }, 740 | 741 | // return all suitable-for-sortable elements in a guaranteed order 742 | findElements: function(element, options) { 743 | return Element.findChildren( 744 | element, options.only, options.tree ? true : false, options.tag); 745 | }, 746 | 747 | findTreeElements: function(element, options) { 748 | return Element.findChildren( 749 | element, options.only, options.tree ? true : false, options.treeTag); 750 | }, 751 | 752 | onHover: function(element, dropon, overlap) { 753 | if(Element.isParent(dropon, element)) return; 754 | 755 | if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { 756 | return; 757 | } else if(overlap>0.5) { 758 | Sortable.mark(dropon, 'before'); 759 | if(dropon.previousSibling != element) { 760 | var oldParentNode = element.parentNode; 761 | element.style.visibility = "hidden"; // fix gecko rendering 762 | dropon.parentNode.insertBefore(element, dropon); 763 | if(dropon.parentNode!=oldParentNode) 764 | Sortable.options(oldParentNode).onChange(element); 765 | Sortable.options(dropon.parentNode).onChange(element); 766 | } 767 | } else { 768 | Sortable.mark(dropon, 'after'); 769 | var nextElement = dropon.nextSibling || null; 770 | if(nextElement != element) { 771 | var oldParentNode = element.parentNode; 772 | element.style.visibility = "hidden"; // fix gecko rendering 773 | dropon.parentNode.insertBefore(element, nextElement); 774 | if(dropon.parentNode!=oldParentNode) 775 | Sortable.options(oldParentNode).onChange(element); 776 | Sortable.options(dropon.parentNode).onChange(element); 777 | } 778 | } 779 | }, 780 | 781 | onEmptyHover: function(element, dropon, overlap) { 782 | var oldParentNode = element.parentNode; 783 | var droponOptions = Sortable.options(dropon); 784 | 785 | if(!Element.isParent(dropon, element)) { 786 | var index; 787 | 788 | var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); 789 | var child = null; 790 | 791 | if(children) { 792 | var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); 793 | 794 | for (index = 0; index < children.length; index += 1) { 795 | if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { 796 | offset -= Element.offsetSize (children[index], droponOptions.overlap); 797 | } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { 798 | child = index + 1 < children.length ? children[index + 1] : null; 799 | break; 800 | } else { 801 | child = children[index]; 802 | break; 803 | } 804 | } 805 | } 806 | 807 | dropon.insertBefore(element, child); 808 | 809 | Sortable.options(oldParentNode).onChange(element); 810 | droponOptions.onChange(element); 811 | } 812 | }, 813 | 814 | unmark: function() { 815 | if(Sortable._marker) Sortable._marker.hide(); 816 | }, 817 | 818 | mark: function(dropon, position) { 819 | // mark on ghosting only 820 | var sortable = Sortable.options(dropon.parentNode); 821 | if(sortable && !sortable.ghosting) return; 822 | 823 | if(!Sortable._marker) { 824 | Sortable._marker = 825 | ($('dropmarker') || Element.extend(document.createElement('DIV'))). 826 | hide().addClassName('dropmarker').setStyle({position:'absolute'}); 827 | document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); 828 | } 829 | var offsets = dropon.cumulativeOffset(); 830 | Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); 831 | 832 | if(position=='after') 833 | if(sortable.overlap == 'horizontal') 834 | Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); 835 | else 836 | Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); 837 | 838 | Sortable._marker.show(); 839 | }, 840 | 841 | _tree: function(element, options, parent) { 842 | var children = Sortable.findElements(element, options) || []; 843 | 844 | for (var i = 0; i < children.length; ++i) { 845 | var match = children[i].id.match(options.format); 846 | 847 | if (!match) continue; 848 | 849 | var child = { 850 | id: encodeURIComponent(match ? match[1] : null), 851 | element: element, 852 | parent: parent, 853 | children: [], 854 | position: parent.children.length, 855 | container: $(children[i]).down(options.treeTag) 856 | }; 857 | 858 | /* Get the element containing the children and recurse over it */ 859 | if (child.container) 860 | this._tree(child.container, options, child); 861 | 862 | parent.children.push (child); 863 | } 864 | 865 | return parent; 866 | }, 867 | 868 | tree: function(element) { 869 | element = $(element); 870 | var sortableOptions = this.options(element); 871 | var options = Object.extend({ 872 | tag: sortableOptions.tag, 873 | treeTag: sortableOptions.treeTag, 874 | only: sortableOptions.only, 875 | name: element.id, 876 | format: sortableOptions.format 877 | }, arguments[1] || { }); 878 | 879 | var root = { 880 | id: null, 881 | parent: null, 882 | children: [], 883 | container: element, 884 | position: 0 885 | }; 886 | 887 | return Sortable._tree(element, options, root); 888 | }, 889 | 890 | /* Construct a [i] index for a particular node */ 891 | _constructIndex: function(node) { 892 | var index = ''; 893 | do { 894 | if (node.id) index = '[' + node.position + ']' + index; 895 | } while ((node = node.parent) != null); 896 | return index; 897 | }, 898 | 899 | sequence: function(element) { 900 | element = $(element); 901 | var options = Object.extend(this.options(element), arguments[1] || { }); 902 | 903 | return $(this.findElements(element, options) || []).map( function(item) { 904 | return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; 905 | }); 906 | }, 907 | 908 | setSequence: function(element, new_sequence) { 909 | element = $(element); 910 | var options = Object.extend(this.options(element), arguments[2] || { }); 911 | 912 | var nodeMap = { }; 913 | this.findElements(element, options).each( function(n) { 914 | if (n.id.match(options.format)) 915 | nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; 916 | n.parentNode.removeChild(n); 917 | }); 918 | 919 | new_sequence.each(function(ident) { 920 | var n = nodeMap[ident]; 921 | if (n) { 922 | n[1].appendChild(n[0]); 923 | delete nodeMap[ident]; 924 | } 925 | }); 926 | }, 927 | 928 | serialize: function(element) { 929 | element = $(element); 930 | var options = Object.extend(Sortable.options(element), arguments[1] || { }); 931 | var name = encodeURIComponent( 932 | (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); 933 | 934 | if (options.tree) { 935 | return Sortable.tree(element, arguments[1]).children.map( function (item) { 936 | return [name + Sortable._constructIndex(item) + "[id]=" + 937 | encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); 938 | }).flatten().join('&'); 939 | } else { 940 | return Sortable.sequence(element, arguments[1]).map( function(item) { 941 | return name + "[]=" + encodeURIComponent(item); 942 | }).join('&'); 943 | } 944 | } 945 | }; 946 | 947 | // Returns true if child is contained within element 948 | Element.isParent = function(child, element) { 949 | if (!child.parentNode || child == element) return false; 950 | if (child.parentNode == element) return true; 951 | return Element.isParent(child.parentNode, element); 952 | }; 953 | 954 | Element.findChildren = function(element, only, recursive, tagName) { 955 | if(!element.hasChildNodes()) return null; 956 | tagName = tagName.toUpperCase(); 957 | if(only) only = [only].flatten(); 958 | var elements = []; 959 | $A(element.childNodes).each( function(e) { 960 | if(e.tagName && e.tagName.toUpperCase()==tagName && 961 | (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) 962 | elements.push(e); 963 | if(recursive) { 964 | var grandchildren = Element.findChildren(e, only, recursive, tagName); 965 | if(grandchildren) elements.push(grandchildren); 966 | } 967 | }); 968 | 969 | return (elements.length>0 ? elements.flatten() : []); 970 | }; 971 | 972 | Element.offsetSize = function (element, type) { 973 | return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; 974 | }; -------------------------------------------------------------------------------- /test/rails/3/public/javascripts/rails.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // Technique from Juriy Zaytsev 3 | // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ 4 | function isEventSupported(eventName) { 5 | var el = document.createElement('div'); 6 | eventName = 'on' + eventName; 7 | var isSupported = (eventName in el); 8 | if (!isSupported) { 9 | el.setAttribute(eventName, 'return;'); 10 | isSupported = typeof el[eventName] == 'function'; 11 | } 12 | el = null; 13 | return isSupported; 14 | } 15 | 16 | function isForm(element) { 17 | return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM' 18 | } 19 | 20 | function isInput(element) { 21 | if (Object.isElement(element)) { 22 | var name = element.nodeName.toUpperCase() 23 | return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA' 24 | } 25 | else return false 26 | } 27 | 28 | var submitBubbles = isEventSupported('submit'), 29 | changeBubbles = isEventSupported('change') 30 | 31 | if (!submitBubbles || !changeBubbles) { 32 | // augment the Event.Handler class to observe custom events when needed 33 | Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap( 34 | function(init, element, eventName, selector, callback) { 35 | init(element, eventName, selector, callback) 36 | // is the handler being attached to an element that doesn't support this event? 37 | if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) || 38 | (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) { 39 | // "submit" => "emulated:submit" 40 | this.eventName = 'emulated:' + this.eventName 41 | } 42 | } 43 | ) 44 | } 45 | 46 | if (!submitBubbles) { 47 | // discover forms on the page by observing focus events which always bubble 48 | document.on('focusin', 'form', function(focusEvent, form) { 49 | // special handler for the real "submit" event (one-time operation) 50 | if (!form.retrieve('emulated:submit')) { 51 | form.on('submit', function(submitEvent) { 52 | var emulated = form.fire('emulated:submit', submitEvent, true) 53 | // if custom event received preventDefault, cancel the real one too 54 | if (emulated.returnValue === false) submitEvent.preventDefault() 55 | }) 56 | form.store('emulated:submit', true) 57 | } 58 | }) 59 | } 60 | 61 | if (!changeBubbles) { 62 | // discover form inputs on the page 63 | document.on('focusin', 'input, select, texarea', function(focusEvent, input) { 64 | // special handler for real "change" events 65 | if (!input.retrieve('emulated:change')) { 66 | input.on('change', function(changeEvent) { 67 | input.fire('emulated:change', changeEvent, true) 68 | }) 69 | input.store('emulated:change', true) 70 | } 71 | }) 72 | } 73 | 74 | function handleRemote(element) { 75 | var method, url, params; 76 | 77 | var event = element.fire("ajax:before"); 78 | if (event.stopped) return false; 79 | 80 | if (element.tagName.toLowerCase() === 'form') { 81 | method = element.readAttribute('method') || 'post'; 82 | url = element.readAttribute('action'); 83 | params = element.serialize(); 84 | } else { 85 | method = element.readAttribute('data-method') || 'get'; 86 | url = element.readAttribute('href'); 87 | params = {}; 88 | } 89 | 90 | new Ajax.Request(url, { 91 | method: method, 92 | parameters: params, 93 | evalScripts: true, 94 | 95 | onComplete: function(request) { element.fire("ajax:complete", request); }, 96 | onSuccess: function(request) { element.fire("ajax:success", request); }, 97 | onFailure: function(request) { element.fire("ajax:failure", request); } 98 | }); 99 | 100 | element.fire("ajax:after"); 101 | } 102 | 103 | function handleMethod(element) { 104 | var method = element.readAttribute('data-method'), 105 | url = element.readAttribute('href'), 106 | csrf_param = $$('meta[name=csrf-param]')[0], 107 | csrf_token = $$('meta[name=csrf-token]')[0]; 108 | 109 | var form = new Element('form', { method: "POST", action: url, style: "display: none;" }); 110 | element.parentNode.insert(form); 111 | 112 | if (method !== 'post') { 113 | var field = new Element('input', { type: 'hidden', name: '_method', value: method }); 114 | form.insert(field); 115 | } 116 | 117 | if (csrf_param) { 118 | var param = csrf_param.readAttribute('content'), 119 | token = csrf_token.readAttribute('content'), 120 | field = new Element('input', { type: 'hidden', name: param, value: token }); 121 | form.insert(field); 122 | } 123 | 124 | form.submit(); 125 | } 126 | 127 | 128 | document.on("click", "*[data-confirm]", function(event, element) { 129 | var message = element.readAttribute('data-confirm'); 130 | if (!confirm(message)) event.stop(); 131 | }); 132 | 133 | document.on("click", "a[data-remote]", function(event, element) { 134 | if (event.stopped) return; 135 | handleRemote(element); 136 | event.stop(); 137 | }); 138 | 139 | document.on("click", "a[data-method]", function(event, element) { 140 | if (event.stopped) return; 141 | handleMethod(element); 142 | event.stop(); 143 | }); 144 | 145 | document.on("submit", function(event) { 146 | var element = event.findElement(), 147 | message = element.readAttribute('data-confirm'); 148 | if (message && !confirm(message)) { 149 | event.stop(); 150 | return false; 151 | } 152 | 153 | var inputs = element.select("input[type=submit][data-disable-with]"); 154 | inputs.each(function(input) { 155 | input.disabled = true; 156 | input.writeAttribute('data-original-value', input.value); 157 | input.value = input.readAttribute('data-disable-with'); 158 | }); 159 | 160 | var element = event.findElement("form[data-remote]"); 161 | if (element) { 162 | handleRemote(element); 163 | event.stop(); 164 | } 165 | }); 166 | 167 | document.on("ajax:after", "form", function(event, element) { 168 | var inputs = element.select("input[type=submit][disabled=true][data-disable-with]"); 169 | inputs.each(function(input) { 170 | input.value = input.readAttribute('data-original-value'); 171 | input.removeAttribute('data-original-value'); 172 | input.disabled = false; 173 | }); 174 | }); 175 | })(); 176 | -------------------------------------------------------------------------------- /test/rails/3/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /test/rails/3/public/stylesheets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/customink/central_logger/72bba43d1990a4ba38b126749f1feb77ea108a4a/test/rails/3/public/stylesheets/.gitkeep -------------------------------------------------------------------------------- /test/rails/3/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /test/rails/3/test: -------------------------------------------------------------------------------- 1 | ../common/test -------------------------------------------------------------------------------- /test/rails/3/vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/customink/central_logger/72bba43d1990a4ba38b126749f1feb77ea108a4a/test/rails/3/vendor/plugins/.gitkeep -------------------------------------------------------------------------------- /test/rails/common/app/controllers/order_controller.rb: -------------------------------------------------------------------------------- 1 | class OrderController < ApplicationController 2 | LOG_MESSAGE = "FOO" 3 | 4 | def index 5 | logger.debug LOG_MESSAGE 6 | logger.add_metadata(:application_name_again => Rails.root.basename.to_s) 7 | render :text => "nothing" 8 | end 9 | 10 | def blow_up 11 | raise OrderController::LOG_MESSAGE 12 | end 13 | 14 | def create 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/rails/common/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LoggerAndRails3 5 | <%= stylesheet_link_tag :all %> 6 | <%= javascript_include_tag :defaults %> 7 | 8 | 9 | 10 | <%= yield %> 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/rails/common/app/views/order/create.html.erb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/customink/central_logger/72bba43d1990a4ba38b126749f1feb77ea108a4a/test/rails/common/app/views/order/create.html.erb -------------------------------------------------------------------------------- /test/rails/common/app/views/order/show.html.erb: -------------------------------------------------------------------------------- 1 |

Order#show

2 |

Find me in app/views/order/show.html.erb

3 | -------------------------------------------------------------------------------- /test/rails/common/test/functional/order_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class OrderControllerTest < ActionController::TestCase 4 | def setup 5 | @central_logger = Rails.logger 6 | @central_logger.reset_collection 7 | common_setup 8 | end 9 | 10 | test "should have log level set" do 11 | assert_equal ActiveSupport::BufferedLogger.const_get(Rails.configuration.log_level.to_s.upcase), Rails.logger.level 12 | end 13 | 14 | test "should have auto flushing set in development" do 15 | assert @central_logger.auto_flushing 16 | end 17 | 18 | test "should log a single record" do 19 | get :index 20 | assert_response :success 21 | assert_equal 1, @collection.find({"controller" => "order","action"=> "index"}).count 22 | end 23 | 24 | test "should log a debug message" do 25 | get :index 26 | assert_equal OrderController::LOG_MESSAGE, @collection.find_one({}, :fields => ["messages"])["messages"]["debug"][0] 27 | end 28 | 29 | test "should log extra metadata" do 30 | get :index 31 | assert_equal Rails.root.basename.to_s, @collection.find_one({}, :fields => "application_name_again")["application_name_again"] 32 | end 33 | 34 | test "should log exceptions" do 35 | assert_raise(RuntimeError, OrderController::LOG_MESSAGE) {get :blow_up} 36 | assert_equal 1, @collection.find_one({"messages.error" => /^#{OrderController::LOG_MESSAGE}/})["messages"]["error"].count 37 | end 38 | 39 | test "should not log passwords" do 40 | post :create, :order => {:password => OrderController::LOG_MESSAGE } 41 | assert_equal 1, @collection.find_one({"params.order.password" => "[FILTERED]"})["params"]["order"].count 42 | end 43 | 44 | test "should set the application name" do 45 | if Rails::VERSION::MAJOR == 3 46 | assert_equal 'LoggerAndRails3', @central_logger.instance_variable_get(:@application_name) 47 | else 48 | # 2 is the base dir, so it assumes it should go up two directories due to 49 | # capistrano deployment 50 | assert_equal 'test', @central_logger.instance_variable_get(:@application_name) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/rails/common/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | if Rails::VERSION::MAJOR == 3 4 | require 'rails/test_help' 5 | else 6 | require 'test_help' 7 | end 8 | 9 | class ActiveSupport::TestCase 10 | def common_setup 11 | @con = @central_logger.mongo_connection 12 | @collection = @con[@central_logger.mongo_collection_name] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/shoulda_macros/log_macros.rb: -------------------------------------------------------------------------------- 1 | module LogMacros 2 | def should_contain_one_log_record 3 | should "contain a log record" do 4 | assert_equal 1, @con[@central_logger.mongo_collection_name].count 5 | end 6 | end 7 | 8 | def should_use_database_name_in_config 9 | should "use the database name in the config file" do 10 | assert_equal "system_log", @central_logger.db_configuration['database'] 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script uses rvm and gemsets to test the gem against all versions of ruby 3 | # (RUBY_VERSIONS) and all rails versions in the test/rails directories 4 | # (RAILS_VERSIONS). 5 | 6 | # If these aren't unset, bundler always uses them to find itself and the 7 | # root project gemfile 8 | unset BUNDLE_BIN_PATH 9 | unset BUNDLE_GEMFILE 10 | 11 | RAILS_VERSIONS='2 3' 12 | RUBY_VERSIONS='1.8.7 1.9.2' 13 | PROJECT_GEMSET='central_logger' 14 | RAILS_DIR=rails 15 | RAILS_RAKE_TASK_MODIFIER=':functionals' 16 | RAKE_UNIT_TASK_MODIFIER=':units' 17 | CLEAN=0 18 | VALID_OPT='--clean' 19 | 20 | function usage() { 21 | echo 'Usage: ' $0 "[${VALID_OPT}]" 22 | echo 'Options:' $VALID_OPT 'Delete and recreate all gemsets before running tests' 23 | exit 1 24 | } 25 | 26 | ARG=$1 27 | if [ $# -gt 1 ] || [ ${ARG:-$VALID_OPT} != $VALID_OPT ]; then 28 | usage 29 | elif [ -n "$1" ]; then 30 | CLEAN=1 31 | fi 32 | 33 | # Load RVM into a shell session *as a function* 34 | if [[ -s "$HOME/.rvm/scripts/rvm" ]]; then 35 | # First try to load from a user install 36 | source "$HOME/.rvm/scripts/rvm" 37 | elif [[ -s "/usr/local/rvm/scripts/rvm" ]]; then 38 | # Then try to load from a root install 39 | source "/usr/local/rvm/scripts/rvm" 40 | else 41 | printf "ERROR: An RVM installation was not found.\n" 42 | exit 1 43 | fi 44 | 45 | # deletes and recreates a gemset 46 | function clean_gemset() { 47 | RUBY=$1; GEMSET=$2 48 | # easier than checking and emptying/creating 49 | rvm --force gemset delete ${RUBY}@${GEMSET} 50 | rvm gemset create $GEMSET 51 | } 52 | 53 | # runs rake test, optionally cleaning the gemset 54 | function rake_test() { 55 | RUBY=$1; GEMSET=$2; SUBTASK=$3 56 | echo 57 | echo "-----------Running tests for '${RUBY}@${GEMSET}'------------" 58 | rvm $RUBY 59 | 60 | if [ $CLEAN -eq 1 ]; then 61 | clean_gemset $RUBY $GEMSET 62 | rvm gemset use $GEMSET 63 | bundle install 64 | else 65 | rvm gemset use $GEMSET 66 | fi 67 | 68 | # 'rvm $RUBY_VER rake' doesn't work with bundle install 69 | rake test${SUBTASK} 70 | if [ $? -ne 0 ]; then exit 1; fi 71 | } 72 | 73 | # Loop through all perms of rubies and rails 74 | cd test 75 | for RBV in $RUBY_VERSIONS; do 76 | rake_test $RBV $PROJECT_GEMSET $RAKE_UNIT_TASK_MODIFIER 77 | 78 | NEXT_DIR=$RAILS_DIR 79 | for RV in $RAILS_VERSIONS; do 80 | if [ "$NEXT_DIR" == "$RAILS_DIR" ]; then 81 | NEXT_DIR=${NEXT_DIR}/${RV} 82 | else 83 | NEXT_DIR=../${RV} 84 | fi 85 | cd $NEXT_DIR 86 | 87 | rake_test $RBV ${PROJECT_GEMSET}_${RV} $RAILS_RAKE_TASK_MODIFIER 88 | done 89 | 90 | cd ../.. 91 | done 92 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'shoulda' 3 | require 'mocha' 4 | # mock rails class 5 | require 'pathname' 6 | require 'rails' 7 | require 'fileutils' 8 | 9 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 10 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 11 | 12 | Shoulda.autoload_macros("#{File.dirname(__FILE__)}/..") 13 | 14 | class Test::Unit::TestCase 15 | CONFIG_DIR = Rails.root.join("config") 16 | SAMPLE_CONFIG_DIR = File.join(CONFIG_DIR, "samples") 17 | DEFAULT_CONFIG = "database.yml" 18 | DEFAULT_CONFIG_WITH_NO_FILE_LOGGING = "database_no_file_logging.yml" 19 | DEFAULT_CONFIG_WITH_AUTH = "database_with_auth.yml" 20 | MONGOID_CONFIG = "mongoid.yml" 21 | REPLICA_SET_CONFIG = "database_replica_set.yml" 22 | LOGGER_CONFIG = "central_logger.yml" 23 | 24 | def log(msg) 25 | @central_logger.mongoize({"id" => 1}) do 26 | @central_logger.debug(msg) 27 | end 28 | end 29 | 30 | def log_params(msg) 31 | @central_logger.mongoize({:params => msg}) 32 | end 33 | 34 | def log_exception(msg) 35 | @central_logger.mongoize({"id" => 1}) do 36 | raise msg 37 | end 38 | end 39 | 40 | def setup_for_config(source, dest=source) 41 | File.delete(File.join(CONFIG_DIR, DEFAULT_CONFIG)) 42 | cp_config(source, dest) 43 | @central_logger.send(:configure) 44 | end 45 | 46 | def cp_config(source, dest=source) 47 | FileUtils.cp(File.join(SAMPLE_CONFIG_DIR, source), File.join(CONFIG_DIR, dest)) 48 | end 49 | 50 | def teardown_for_config(file) 51 | File.delete(File.join(CONFIG_DIR, file)) 52 | end 53 | 54 | def log_metadata(options) 55 | @central_logger.mongoize({"id" => 1}) do 56 | @central_logger.add_metadata(options) 57 | end 58 | end 59 | 60 | def require_bogus_active_record 61 | require 'active_record' 62 | end 63 | 64 | def common_setup 65 | @con = @central_logger.mongo_connection 66 | @collection = @con[@central_logger.mongo_collection_name] 67 | end 68 | 69 | def create_user 70 | db_conf = @central_logger.db_configuration 71 | @user = db_conf['username'] 72 | mongo_connection = Mongo::Connection.new(db_conf['host'], 73 | db_conf['port']).db(db_conf['database']) 74 | mongo_connection.add_user(@user, db_conf['password']) 75 | end 76 | 77 | def remove_user 78 | @central_logger.mongo_connection.remove_user(@user) 79 | end 80 | 81 | end 82 | -------------------------------------------------------------------------------- /test/unit/central_logger_replica_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'central_logger/mongo_logger' 3 | 4 | # test the basic stuff 5 | class CentralLogger::MongoLoggerReplicaTest < Test::Unit::TestCase 6 | extend LogMacros 7 | 8 | context "A CentralLogger::MongoLogger" do 9 | setup do 10 | # Can use different configs, but most tests use database.yml 11 | cp_config(REPLICA_SET_CONFIG, DEFAULT_CONFIG) 12 | @central_logger = CentralLogger::MongoLogger.new 13 | @central_logger.reset_collection 14 | end 15 | 16 | context "upon trying to insert into a replica set voting on a new master" do 17 | setup do 18 | puts "Please disconnect the current master and hit ENTER" 19 | STDIN.gets 20 | end 21 | 22 | should "insert a record successfully" do 23 | assert_nothing_raised{ log("Test") } 24 | @central_logger.rescue_connection_failure do 25 | assert_equal 1, @central_logger.mongo_collection.count 26 | end 27 | end 28 | 29 | teardown do 30 | puts "Please reconnect the current master, wait for the vote to complete, then hit ENTER" 31 | STDIN.gets 32 | end 33 | end 34 | 35 | should "insert a record successfully" do 36 | assert_nothing_raised{ log("Test") } 37 | assert_equal 1, @central_logger.mongo_collection.count 38 | end 39 | 40 | teardown do 41 | file = File.join(CONFIG_DIR, DEFAULT_CONFIG) 42 | File.delete(file) if File.exist?(file) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/unit/central_logger_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'central_logger/mongo_logger' 3 | require 'tempfile' 4 | require 'pathname' 5 | 6 | # test the basic stuff 7 | class CentralLogger::MongoLoggerTest < Test::Unit::TestCase 8 | extend LogMacros 9 | 10 | EXCEPTION_MSG = "Foo" 11 | 12 | context "A CentralLogger::MongoLogger" do 13 | setup do 14 | # Can use different configs, but most tests use database.yml 15 | FileUtils.cp(File.join(SAMPLE_CONFIG_DIR, DEFAULT_CONFIG), CONFIG_DIR) 16 | end 17 | 18 | context "in instantiation" do 19 | setup do 20 | CentralLogger::MongoLogger.any_instance.stubs(:internal_initialize).returns(nil) 21 | CentralLogger::MongoLogger.any_instance.stubs(:disable_file_logging?).returns(false) 22 | @central_logger = CentralLogger::MongoLogger.new 23 | end 24 | 25 | context "during configuration when using a separate " + LOGGER_CONFIG do 26 | setup do 27 | setup_for_config(LOGGER_CONFIG) 28 | end 29 | 30 | should_use_database_name_in_config 31 | 32 | teardown do 33 | teardown_for_config(LOGGER_CONFIG) 34 | end 35 | end 36 | 37 | context "during configuration when using a separate " + MONGOID_CONFIG do 38 | setup do 39 | setup_for_config(MONGOID_CONFIG) 40 | end 41 | 42 | should_use_database_name_in_config 43 | 44 | teardown do 45 | teardown_for_config(MONGOID_CONFIG) 46 | end 47 | end 48 | 49 | # this test will work without the --auth mongod arg 50 | context "upon connecting with authentication settings" do 51 | setup do 52 | setup_for_config(DEFAULT_CONFIG_WITH_AUTH, DEFAULT_CONFIG) 53 | create_user 54 | end 55 | 56 | should "authenticate with the credentials in the configuration" do 57 | @central_logger.send(:connect) 58 | assert @central_logger.authenticated? 59 | end 60 | 61 | teardown do 62 | # config will be deleted by outer teardown 63 | remove_user 64 | end 65 | end 66 | 67 | context "after configuration" do 68 | setup do 69 | @central_logger.send(:configure) 70 | end 71 | 72 | should "set the default host, port, and capsize if not configured" do 73 | assert_equal 'localhost', @central_logger.db_configuration['host'] 74 | assert_equal 27017, @central_logger.db_configuration['port'] 75 | assert_equal CentralLogger::MongoLogger::DEFAULT_COLLECTION_SIZE, @central_logger.db_configuration['capsize'] 76 | end 77 | 78 | should "set the mongo collection name depending on the Rails environment" do 79 | assert_equal "#{Rails.env}_log", @central_logger.mongo_collection_name 80 | end 81 | 82 | should "set the application name when specified in the config file" do 83 | assert_equal "central_foo", @central_logger.instance_variable_get(:@application_name) 84 | end 85 | 86 | should "set safe insert when specified in the config file" do 87 | assert @central_logger.instance_variable_get(:@safe_insert) 88 | end 89 | 90 | should "use the database name in the config file" do 91 | assert_equal "system_log", @central_logger.db_configuration['database'] 92 | end 93 | 94 | context "upon connecting to an empty database" do 95 | setup do 96 | @central_logger.send(:connect) 97 | common_setup 98 | @collection.drop 99 | end 100 | 101 | should "expose a valid mongo connection" do 102 | assert_instance_of Mongo::DB, @central_logger.mongo_connection 103 | end 104 | 105 | should "not authenticate" do 106 | assert !@central_logger.authenticated? 107 | end 108 | 109 | should "create a capped collection in the database with the configured size" do 110 | @central_logger.send(:check_for_collection) 111 | assert @con.collection_names.include?(@central_logger.mongo_collection_name) 112 | # new capped collections are X MB + 5888 bytes, but don't be too strict in case that changes 113 | assert @collection.stats["storageSize"] < CentralLogger::MongoLogger::DEFAULT_COLLECTION_SIZE + 1.megabyte 114 | end 115 | end 116 | end 117 | end 118 | 119 | context "after instantiation" do 120 | setup do 121 | @central_logger = CentralLogger::MongoLogger.new 122 | common_setup 123 | @central_logger.reset_collection 124 | end 125 | 126 | context "upon insertion of a log record when active record is not used" do 127 | # mock ActiveRecord has not been included 128 | setup do 129 | log("Test") 130 | end 131 | 132 | should_contain_one_log_record 133 | 134 | should "allow recreation of the capped collection to remove all records" do 135 | @central_logger.reset_collection 136 | assert_equal 0, @collection.count 137 | end 138 | end 139 | 140 | context "upon insertion of a colorized log record when ActiveRecord is used" do 141 | setup do 142 | @log_message = "TESTING" 143 | require_bogus_active_record 144 | log("\e[31m #{@log_message} \e[0m") 145 | end 146 | 147 | should "detect logging is colorized" do 148 | assert @central_logger.send(:logging_colorized?) 149 | end 150 | 151 | should_contain_one_log_record 152 | 153 | should "strip out colorization from log messages" do 154 | assert_equal 1, @collection.find({"messages.debug" => @log_message}).count 155 | end 156 | end 157 | 158 | should "add application metadata to the log record" do 159 | options = {"application" => self.class.name} 160 | log_metadata(options) 161 | assert_equal 1, @collection.find({"application" => self.class.name}).count 162 | end 163 | 164 | should "not raise an exception when bson-unserializable data is logged in the :messages key" do 165 | log(Tempfile.new("foo")) 166 | assert_equal 1, @collection.count 167 | end 168 | 169 | should "not raise an exception when bson-unserializable data is logged in the :params key" do 170 | log_params({:foo => Tempfile.new("bar")}) 171 | assert_equal 1, @collection.count 172 | end 173 | 174 | context "when an exception is raised" do 175 | should "log the exception" do 176 | assert_raise(RuntimeError, EXCEPTION_MSG) {log_exception(EXCEPTION_MSG)} 177 | assert_equal 1, @collection.find_one({"messages.error" => /^#{EXCEPTION_MSG}/})["messages"]["error"].count 178 | end 179 | end 180 | end 181 | 182 | context "logging at INFO level" do 183 | setup do 184 | @central_logger = CentralLogger::MongoLogger.new(:level => CentralLogger::MongoLogger::INFO) 185 | common_setup 186 | @central_logger.reset_collection 187 | log("INFO") 188 | end 189 | 190 | should_contain_one_log_record 191 | 192 | should "not log DEBUG messages" do 193 | assert_equal 0, @collection.find_one({}, :fields => ["messages"])["messages"].count 194 | end 195 | end 196 | teardown do 197 | file = File.join(CONFIG_DIR, DEFAULT_CONFIG) 198 | File.delete(file) if File.exist?(file) 199 | end 200 | end 201 | 202 | context "A CentralLogger::MongoLogger without file logging" do 203 | setup do 204 | FileUtils.cp(File.join(SAMPLE_CONFIG_DIR, DEFAULT_CONFIG_WITH_NO_FILE_LOGGING), File.join(CONFIG_DIR, DEFAULT_CONFIG)) 205 | @log_file = Pathname.new('log.out') 206 | FileUtils.touch(@log_file) 207 | end 208 | 209 | context "in instantiation" do 210 | should "not call super in the initialize method" do 211 | CentralLogger::MongoLogger.any_instance.expects(:open).never # Stubbing out super doesn't work, so we use this side effect instead. 212 | CentralLogger::MongoLogger.new 213 | end 214 | 215 | should "set level" do 216 | level = stub('level') 217 | logger = CentralLogger::MongoLogger.new(:level => level) 218 | assert_equal level, logger.level 219 | end 220 | should "set buffer" do 221 | assert_equal({}, CentralLogger::MongoLogger.new.instance_variable_get(:@buffer)) 222 | end 223 | should "set auto flushing" do 224 | assert_equal 1, CentralLogger::MongoLogger.new.instance_variable_get(:@auto_flushing) 225 | end 226 | should "set guard" do 227 | assert CentralLogger::MongoLogger.new.instance_variable_get(:@guard).is_a?(Mutex) 228 | end 229 | end 230 | 231 | context "after instantiation" do 232 | context "upon insertion of a log record" do 233 | setup do 234 | @central_logger = CentralLogger::MongoLogger.new(:path => @log_file) 235 | log("Test") 236 | end 237 | 238 | should "not log the record to a file" do 239 | assert_equal '', open(@log_file).read 240 | end 241 | end 242 | end 243 | 244 | teardown do 245 | file = File.join(CONFIG_DIR, DEFAULT_CONFIG) 246 | File.delete(file) if File.exist?(file) 247 | File.delete(@log_file) 248 | end 249 | end 250 | end 251 | --------------------------------------------------------------------------------