adding first revision for deployment
|
@ -0,0 +1,14 @@
|
||||||
|
BinaryAttraction
|
||||||
|
|
||||||
|
A little social program i am writing for my senior design class. This site will
|
||||||
|
let you vote in the new digital age on photos.
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
* Merb
|
||||||
|
* Haml / Sass
|
||||||
|
* Merb Helpers
|
||||||
|
* Merb Has Flash
|
||||||
|
* Recaptcha account
|
||||||
|
|
||||||
|
Released under GNU GPLv2
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
require 'rubygems'
|
||||||
|
Gem.clear_paths
|
||||||
|
Gem.path.unshift(File.join(File.dirname(__FILE__), "gems"))
|
||||||
|
|
||||||
|
require 'rake'
|
||||||
|
require 'rake/rdoctask'
|
||||||
|
require 'rake/testtask'
|
||||||
|
require 'spec/rake/spectask'
|
||||||
|
require 'fileutils'
|
||||||
|
|
||||||
|
##
|
||||||
|
# requires frozen merb-core (from /framework)
|
||||||
|
# adds the other components to the load path
|
||||||
|
def require_frozen_framework
|
||||||
|
framework = File.join(File.dirname(__FILE__), "framework")
|
||||||
|
if File.directory?(framework)
|
||||||
|
puts "Running from frozen framework"
|
||||||
|
core = File.join(framework,"merb-core")
|
||||||
|
if File.directory?(core)
|
||||||
|
puts "using merb-core from #{core}"
|
||||||
|
$:.unshift File.join(core,"lib")
|
||||||
|
require 'merb-core'
|
||||||
|
end
|
||||||
|
more = File.join(framework,"merb-more")
|
||||||
|
if File.directory?(more)
|
||||||
|
Dir.new(more).select {|d| d =~ /merb-/}.each do |d|
|
||||||
|
$:.unshift File.join(more,d,'lib')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
plugins = File.join(framework,"merb-plugins")
|
||||||
|
if File.directory?(plugins)
|
||||||
|
Dir.new(plugins).select {|d| d =~ /merb_/}.each do |d|
|
||||||
|
$:.unshift File.join(plugins,d,'lib')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
require "merb-core/core_ext/kernel"
|
||||||
|
require "merb-core/core_ext/rubygems"
|
||||||
|
else
|
||||||
|
p "merb doesn't seem to be frozen in /framework"
|
||||||
|
require 'merb-core'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ENV['FROZEN']
|
||||||
|
require_frozen_framework
|
||||||
|
else
|
||||||
|
require 'merb-core'
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'merb-core/tasks/merb'
|
||||||
|
include FileUtils
|
||||||
|
|
||||||
|
# Load the basic runtime dependencies; this will include
|
||||||
|
# any plugins and therefore plugin rake tasks.
|
||||||
|
init_env = ENV['MERB_ENV'] || 'rake'
|
||||||
|
Merb.load_dependencies(:environment => init_env)
|
||||||
|
|
||||||
|
# Get Merb plugins and dependencies
|
||||||
|
Merb::Plugins.rakefiles.each { |r| require r }
|
||||||
|
|
||||||
|
# Load any app level custom rakefile extensions from lib/tasks
|
||||||
|
tasks_path = File.join(File.dirname(__FILE__), "lib", "tasks")
|
||||||
|
rake_files = Dir["#{tasks_path}/*.rake"]
|
||||||
|
rake_files.each{|rake_file| load rake_file }
|
||||||
|
|
||||||
|
|
||||||
|
desc "start runner environment"
|
||||||
|
task :merb_env do
|
||||||
|
Merb.start_environment(:environment => init_env, :adapter => 'runner')
|
||||||
|
end
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# ADD YOUR CUSTOM TASKS IN /lib/tasks
|
||||||
|
# NAME YOUR RAKE FILES file_name.rake
|
||||||
|
##############################################################################
|
|
@ -0,0 +1,9 @@
|
||||||
|
class Application < Merb::Controller
|
||||||
|
def current_user
|
||||||
|
(@user ||= User.find(session[:user_id]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset_session
|
||||||
|
session[:user_id] = nil
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,13 @@
|
||||||
|
class Exceptions < Application
|
||||||
|
|
||||||
|
# handle NotFound exceptions (404)
|
||||||
|
def not_found
|
||||||
|
render :format => :html
|
||||||
|
end
|
||||||
|
|
||||||
|
# handle NotAcceptable exceptions (406)
|
||||||
|
def not_acceptable
|
||||||
|
render :format => :html
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
class Favorites < Application
|
||||||
|
|
||||||
|
# ...and remember, everything returned from an action
|
||||||
|
# goes to the client...
|
||||||
|
def index
|
||||||
|
render
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
class Home < Application
|
||||||
|
def index
|
||||||
|
render
|
||||||
|
end
|
||||||
|
|
||||||
|
def acceptable_use
|
||||||
|
render
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
class Photos < Application
|
||||||
|
|
||||||
|
# ...and remember, everything returned from an action
|
||||||
|
# goes to the client...
|
||||||
|
def index
|
||||||
|
render
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,27 @@
|
||||||
|
class Sessions < Application
|
||||||
|
def index
|
||||||
|
redirect '/'
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
render
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
user = User.find_by_user_name params[:user_name]
|
||||||
|
if user.authenticated_against?(params[:password])
|
||||||
|
session[:user_id] = user.id
|
||||||
|
flash[:notice] = 'Great success!'
|
||||||
|
redirect '/'
|
||||||
|
else
|
||||||
|
flash[:error] = 'Login failed'
|
||||||
|
render :new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete
|
||||||
|
reset_session
|
||||||
|
flash[:notice] = 'Goodbye!'
|
||||||
|
redirect '/'
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
class Stats < Application
|
||||||
|
|
||||||
|
# ...and remember, everything returned from an action
|
||||||
|
# goes to the client...
|
||||||
|
def index
|
||||||
|
render
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,71 @@
|
||||||
|
class Users < Application
|
||||||
|
before :prepare_user, :only => [ :show, :edit, :update, :delete ]
|
||||||
|
|
||||||
|
include Ambethia::ReCaptcha::Controller
|
||||||
|
|
||||||
|
def index
|
||||||
|
if current_user.administrator?
|
||||||
|
@users = User.find :all, :order => 'user_name ASC'
|
||||||
|
render
|
||||||
|
else
|
||||||
|
redirect url(:user, :id => current_user.user_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@user = User.new
|
||||||
|
render
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@user = User.new params[:user]
|
||||||
|
@user.user_name = params[:user][:user_name] rescue nil
|
||||||
|
if verify_recaptcha(@user) and @user.save
|
||||||
|
flash[:notice] = 'Great success'
|
||||||
|
redirect '/'
|
||||||
|
else
|
||||||
|
flash[:error] = 'The user could not be created...'
|
||||||
|
render :new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
render
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
if @user.save
|
||||||
|
flash[:notice] = 'Great success'
|
||||||
|
redirect url(:users)
|
||||||
|
else
|
||||||
|
render :edit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete
|
||||||
|
if @user.destroy
|
||||||
|
flash[:notice] = "Epic failure, goodbye #{@user.user_name}"
|
||||||
|
reset_session if @user.id == session[:user_id]
|
||||||
|
else
|
||||||
|
flash[:error] = 'That does not work...'
|
||||||
|
end
|
||||||
|
redirect url(:users)
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def prepare_user
|
||||||
|
@user = if current_user.administrator?
|
||||||
|
User.find_by_user_name params[:id]
|
||||||
|
else
|
||||||
|
current_user
|
||||||
|
end
|
||||||
|
raise NotFound if @user.nil?
|
||||||
|
@user.attributes = params[:user] if params[:user] and request.post?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
class Votes < Application
|
||||||
|
|
||||||
|
# ...and remember, everything returned from an action
|
||||||
|
# goes to the client...
|
||||||
|
def index
|
||||||
|
render
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
module Merb
|
||||||
|
module FavoritesHelper
|
||||||
|
|
||||||
|
end
|
||||||
|
end # Merb
|
|
@ -0,0 +1,15 @@
|
||||||
|
module Merb
|
||||||
|
module GlobalHelpers
|
||||||
|
def logged_in?
|
||||||
|
!session[:user_id].nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def error_messages(obj)
|
||||||
|
if obj.errors.empty?
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
"<div id='model_errors'><ul>#{obj.errors.each_full { |msg| "<li>#{msg}</li>" }}</ul></div>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
module Merb
|
||||||
|
module HomeHelper
|
||||||
|
|
||||||
|
end
|
||||||
|
end # Merb
|
|
@ -0,0 +1,5 @@
|
||||||
|
module Merb
|
||||||
|
module PhotosHelper
|
||||||
|
|
||||||
|
end
|
||||||
|
end # Merb
|
|
@ -0,0 +1,5 @@
|
||||||
|
module Merb
|
||||||
|
module SessionHelper
|
||||||
|
|
||||||
|
end
|
||||||
|
end # Merb
|
|
@ -0,0 +1,5 @@
|
||||||
|
module Merb
|
||||||
|
module StatsHelper
|
||||||
|
|
||||||
|
end
|
||||||
|
end # Merb
|
|
@ -0,0 +1,5 @@
|
||||||
|
module Merb
|
||||||
|
module UsersHelper
|
||||||
|
include Ambethia::ReCaptcha::Helper
|
||||||
|
end
|
||||||
|
end # Merb
|
|
@ -0,0 +1,5 @@
|
||||||
|
module Merb
|
||||||
|
module VotesHelper
|
||||||
|
|
||||||
|
end
|
||||||
|
end # Merb
|
|
@ -0,0 +1,9 @@
|
||||||
|
module Merb
|
||||||
|
module Session
|
||||||
|
|
||||||
|
# The Merb::Session module gets mixed into Merb::SessionContainer to allow
|
||||||
|
# app-level functionality; it will be included and methods will be available
|
||||||
|
# through request.session as instance methods.
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
class Photo < ActiveRecord::Base
|
||||||
|
#property :id, Integer, :serial => true
|
||||||
|
#property :filename, String
|
||||||
|
#property :email_hash, String
|
||||||
|
#property :created_at, DateTime
|
||||||
|
validates_presence_of :filename
|
||||||
|
has_many :votes, :dependent => :destroy
|
||||||
|
end
|
|
@ -0,0 +1,44 @@
|
||||||
|
class User < ActiveRecord::Base
|
||||||
|
attr_accessor :password, :password_confirmation
|
||||||
|
attr_protected :user_name
|
||||||
|
attr_protected :auth_token
|
||||||
|
attr_protected :authorized
|
||||||
|
|
||||||
|
validates_presence_of :user_name
|
||||||
|
validates_length_of :user_name, :within => (6..32)
|
||||||
|
validates_uniqueness_of :user_name
|
||||||
|
validates_format_of :user_name, :with => /[\w_-]+/
|
||||||
|
|
||||||
|
has_many :votes, :dependent => :destroy
|
||||||
|
|
||||||
|
before_validation :saltify_password
|
||||||
|
|
||||||
|
def authenticated_against?(str)
|
||||||
|
ss = User.salted_string(str)
|
||||||
|
if self.auth_token.to_s == ss
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.salted_string(str)
|
||||||
|
Digest::SHA1.hexdigest("#{Merb::Config[:session_secret_key]}--#{str}--")
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def saltify_password
|
||||||
|
if !self.password.to_s.empty?
|
||||||
|
if self.password.to_s.size < 6
|
||||||
|
self.errors.add(:password, 'is too short')
|
||||||
|
elsif self.password != self.password_confirmation
|
||||||
|
self.errors.add(:passwords, 'do not match')
|
||||||
|
else
|
||||||
|
self.auth_token = User.salted_string(self.password)
|
||||||
|
end
|
||||||
|
elsif self.auth_token.to_s.empty?
|
||||||
|
self.errors.add(:password, 'is missing')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,34 @@
|
||||||
|
class Vote < ActiveRecord::Base
|
||||||
|
belongs_to :photo
|
||||||
|
belongs_to :user
|
||||||
|
|
||||||
|
validates_presence_of :vote
|
||||||
|
|
||||||
|
##
|
||||||
|
# Checks if this vote is anonymous, or not an authenticated User vote.
|
||||||
|
#
|
||||||
|
def anonymous?
|
||||||
|
self.user_id.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Convert this Vote to a number.
|
||||||
|
#
|
||||||
|
def to_i
|
||||||
|
self.vote? ? 1 : 0
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Is this a 'no' vote.
|
||||||
|
#
|
||||||
|
def zero?
|
||||||
|
self.to_i == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Is this a 'yes' vote.
|
||||||
|
#
|
||||||
|
def one?
|
||||||
|
self.to_i == 1
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,216 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||||
|
<title><%= @exception_name %></title>
|
||||||
|
<style type="text/css" media="screen">
|
||||||
|
body {
|
||||||
|
font-family:arial;
|
||||||
|
font-size:11px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size:48px;
|
||||||
|
letter-spacing:-4px;
|
||||||
|
margin:0;
|
||||||
|
line-height:36px;
|
||||||
|
color:#333;
|
||||||
|
}
|
||||||
|
h1 sup {
|
||||||
|
font-size: 0.5em;
|
||||||
|
}
|
||||||
|
h1 sup.error_500, h1 sup.error_400 {
|
||||||
|
color:#990E05;
|
||||||
|
}
|
||||||
|
h1 sup.error_100, h1 sup.error_200 {
|
||||||
|
color:#00BF10;
|
||||||
|
}
|
||||||
|
h1 sup.error_300 {
|
||||||
|
/* pretty sure you cant 'see' status 300
|
||||||
|
errors but if you could I think they
|
||||||
|
would be blue */
|
||||||
|
color:#1B2099;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size:36px;
|
||||||
|
letter-spacing:-3px;
|
||||||
|
margin:0;
|
||||||
|
line-height:28px;
|
||||||
|
color:#444;
|
||||||
|
}
|
||||||
|
a, a:visited {
|
||||||
|
color:#00BF10;
|
||||||
|
}
|
||||||
|
.internalError {
|
||||||
|
width:800px;
|
||||||
|
margin:50px auto;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
border-bottom:10px solid #333;
|
||||||
|
margin-bottom:1px;
|
||||||
|
background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7");
|
||||||
|
padding:20px;
|
||||||
|
}
|
||||||
|
table.trace {
|
||||||
|
width:100%;
|
||||||
|
font-family:courier, monospace;
|
||||||
|
letter-spacing:-1px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing:0;
|
||||||
|
}
|
||||||
|
table.trace tr td{
|
||||||
|
padding:0;
|
||||||
|
height:26px;
|
||||||
|
font-size:13px;
|
||||||
|
vertical-align:middle;
|
||||||
|
}
|
||||||
|
table.trace tr.file{
|
||||||
|
border-top:2px solid #fff;
|
||||||
|
background-color:#F3F3F3;
|
||||||
|
}
|
||||||
|
table.trace tr.source {
|
||||||
|
background-color:#F8F8F8;
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
table.trace .open tr.source {
|
||||||
|
display:table-row;
|
||||||
|
}
|
||||||
|
table.trace tr.file td.expand {
|
||||||
|
width:23px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAXCAIAAABvSEP3AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdVJREFUeNqMVL+TwUAYxaRIOlEhlZHGDAUzzOQ61+AqXMV1lJSU7q/QRqm8KFUcJTNn5qJkaPyoKKVz7y4mF8na5Kt29tt9+/Z97/u81+vVQ4r9frdarS6Xi7ETDIZisRjxMGPfmk4niNPpZE+xLAugbPaZ53nzvtfMBe/3+/3dbuehBrAKhZdUKkVAWa9Xsiybv0CPZDJZLr/qa5/BwgwRjYqOKIvFYjQa/aNommZh0Ww2K5UqzwfoQOPxaLPZ3FAmk0+7lplMpt1u53J5OpBOR0eZEE9wHJfP5zud93g88QhluwWbjW+5VOmKBgKBer3eaDTDYeGBQF8+x7rqIYoiPgixWJazpA6HA+MSxRArkUgMh0M409g8Ho8+9wYxxCqVSq1W26EDHGM2m4HOHQrEc38f/Yn7cLmlIRhBENzcx8cVRZnPZ/YUep2BWkjTIfA+PKVpZAXR5QxsjiqCKvGEqqp443w+0dvy17swqD0HB3S73V5PpkNg1qBqt8kwGCjmPkinM0QJbIoEa7U6UG6ToVgs4V9G2g0ESoP5Aoi7KYX5oCgf8IKbkvn9/mr1LRQKESamzgJy0g0tSZIuB3nuGqRU9Vv9C4sKkUhEkp4soxvxI8AAhWrrtXa3X8EAAAAASUVORK5CYII=);
|
||||||
|
background-position:top left;
|
||||||
|
background-repeat:no-repeat;
|
||||||
|
}
|
||||||
|
table.trace .open tr.file td.expand {
|
||||||
|
width:19px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXZJREFUeNrslK1ywkAUhcMOBomEOiSdqLxEBJX0NaijOsjyHGGmCGyQQYaiiiw4gktkcOmZbpsuuzQ/M5XnqJ2d3S/n3nM3rTzPLUP7/Tt0+pLcGQwG3W53OLyHzPMtjYL7q9UqSRLrD4E1Gj1orCvKYuFHUWTVkOM44/HjDcp8/lL4r6NerzeZPMm1KFw0QkDn83m5fP2lHA4fNQvRtNvtjsfDd0WzmSfb2e/fdTqdOvdh/HLJZLOn0+d2HJ+KRGzbdl23EpFlmed5cp2maRzHQq1lvQ5KMi6EUZBGfup6E1pTfd+vrGW7jbQ2C9hTt9BpqNyIWaAwAy6xg2eBz5iRC/NomiZhGN5sqmnkauo0BUGgVQoBjQ80oCACgNQdZHfTYBkF2mxCtWWAqunWpahxIDUt3QYUxIFQpJHyIWpXjinabKbbwItMHT+NyjchrP8QKaSQQgoppJBCCimkkEIKKaSQQgoppJBCCimkkEIKKaSo+hRgAEFD17X08O2NAAAAAElFTkSuQmCC);
|
||||||
|
background-position:top left;
|
||||||
|
background-repeat:no-repeat;
|
||||||
|
}
|
||||||
|
table.trace tr.source td.collapse {
|
||||||
|
width:19px;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVxJREFUeNrs0zFygkAUBmBlUkgJHdABlQwVkVJKKUxBYWbkALTxMJwhltyDFkss03IF8pudIcwaDaDl/6pd2P327b7d+eHwMXs4lNkzggoVKlSoUKFChQoVKlSoUKFChQoVKlSoUKFChQqVEYqm6ft9+qiSJEkYho7jTlcw2fd9NOI4nq4gEdFwXXe1Cqco63VkWVbXRTqLhTpOwQRpF7quR1E0TgGhqvLKUFCyoQqG/rks3O6kZKW/eRFpevOCoGTXVTcMQ5EyxyDEkML1c5RzuZOICIyXqn7JBVez6282MWrx731HOv2qB8Hri2lamNk0DfpVVdV1Peodappmmua8bdvzuc7zfNprzrLMth1FnGh/X8MjCAIQv/cFz/+65PcDh7rbvYv2ZUfdj+PxsyzLgVl0hKwgTqeqKApx2LeOc7t98zyv/1FWOgvx9RPii23bmL9cetJ8Ed8CDAC6aFW8bCzFhwAAAABJRU5ErkJggg==);
|
||||||
|
background-position:bottom left;
|
||||||
|
background-repeat:no-repeat;
|
||||||
|
background-color:#6F706F;
|
||||||
|
}
|
||||||
|
table.trace tr td.path {
|
||||||
|
padding-left:10px;
|
||||||
|
}
|
||||||
|
table.trace tr td.code {
|
||||||
|
padding-left:35px;
|
||||||
|
white-space: pre;
|
||||||
|
line-height:9px;
|
||||||
|
padding-bottom:10px;
|
||||||
|
}
|
||||||
|
table.trace tr td.code em {
|
||||||
|
font-weight:bold;
|
||||||
|
color:#00BF10;
|
||||||
|
}
|
||||||
|
table.trace tr td.code a {
|
||||||
|
width: 20px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
table.trace tr td.code .more {
|
||||||
|
color:#666;
|
||||||
|
}
|
||||||
|
table.trace tr td.line {
|
||||||
|
width:30px;
|
||||||
|
text-align:right;
|
||||||
|
padding-right:4px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
margin-top:5px;
|
||||||
|
font-size:11px;
|
||||||
|
color:#444;
|
||||||
|
text-align:right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="internalError">
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<h1><%= @exception_name %> <sup class="error_<%= @exception.class::STATUS %>"><%= @exception.class::STATUS %></sup></h1>
|
||||||
|
<% if show_details = ::Merb::Config[:exception_details] -%>
|
||||||
|
<h2><%=h @exception.message %></h2>
|
||||||
|
<% else -%>
|
||||||
|
<h2>Sorry about that...</h2>
|
||||||
|
<% end -%>
|
||||||
|
<h3>Parameters</h3>
|
||||||
|
<ul>
|
||||||
|
<% params[:original_params].each do |param, value| %>
|
||||||
|
<li><strong><%= param %>:</strong> <%= value.inspect %></li>
|
||||||
|
<% end %>
|
||||||
|
<%= "<li>None</li>" if params[:original_params].empty? %>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Session</h3>
|
||||||
|
<ul>
|
||||||
|
<% params[:original_session].each do |param, value| %>
|
||||||
|
<li><strong><%= param %>:</strong> <%= value.inspect %></li>
|
||||||
|
<% end %>
|
||||||
|
<%= "<li>None</li>" if params[:original_session].empty? %>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>Cookies</h3>
|
||||||
|
<ul>
|
||||||
|
<% params[:original_cookies].each do |param, value| %>
|
||||||
|
<li><strong><%= param %>:</strong> <%= value.inspect %></li>
|
||||||
|
<% end %>
|
||||||
|
<%= "<li>None</li>" if params[:original_cookies].empty? %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if show_details %>
|
||||||
|
<table class="trace">
|
||||||
|
<% @exception.backtrace.each_with_index do |line, index| %>
|
||||||
|
<tbody class="close">
|
||||||
|
<tr class="file">
|
||||||
|
<td class="expand">
|
||||||
|
</td>
|
||||||
|
<td class="path">
|
||||||
|
<%= (line.match(/^([^:]+)/)[1] rescue 'unknown').sub(/\/((opt|usr)\/local\/lib\/(ruby\/)?(gems\/)?(1.8\/)?(gems\/)?|.+\/app\/)/, '') %>
|
||||||
|
<% unless line.match(/\.erb:/) %>
|
||||||
|
in "<strong><%= line.match(/:in `(.+)'$/)[1] rescue '?' %></strong>"
|
||||||
|
<% else %>
|
||||||
|
(<strong>ERB Template</strong>)
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
<td class="line">
|
||||||
|
<a href="txmt://open?url=file://<%=file = (line.match(/^([^:]+)/)[1] rescue 'unknown')%>&line=<%= lineno = line.match(/:([0-9]+):/)[1] rescue '?' %>"><%=lineno%></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="source">
|
||||||
|
<td class="collapse">
|
||||||
|
</td>
|
||||||
|
<td class="code" colspan="2"><% (__caller_lines__(file, lineno, 5) rescue []).each do |llineno, lcode, lcurrent| %>
|
||||||
|
<a href="txmt://open?url=file://<%=file%>&line=<%=llineno%>"><%= llineno %></a><%='<em>' if llineno==lineno.to_i %><%= lcode.size > 90 ? CGI.escapeHTML(lcode[0..90])+'<span class="more">......</span>' : CGI.escapeHTML(lcode) %><%='</em>' if llineno==lineno.to_i %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<% end %>
|
||||||
|
</table>
|
||||||
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
// swop the open & closed classes
|
||||||
|
els = document.getElementsByTagName('td');
|
||||||
|
for(i=0; i<els.length; i++){
|
||||||
|
if(els[i].className=='expand' || els[i].className=='collapse'){
|
||||||
|
els[i].onclick = function(e){
|
||||||
|
tbody = this.parentNode.parentNode;
|
||||||
|
if(tbody.className=='open'){
|
||||||
|
tbody.className='closed';
|
||||||
|
}else{
|
||||||
|
tbody.className='open';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<% end %>
|
||||||
|
<div class="footer">
|
||||||
|
lots of love, from <a href="#">merb</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,63 @@
|
||||||
|
<div id="container">
|
||||||
|
<div id="header-container">
|
||||||
|
<img src="/images/merb.jpg" />
|
||||||
|
<!-- <h1>Mongrel + Erb</h1> -->
|
||||||
|
<h2>pocket rocket web framework</h2>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="left-container">
|
||||||
|
<h3>Exception:</h3>
|
||||||
|
<p><%= params[:exception] %></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="main-container">
|
||||||
|
<h3>Why am I seeing this page?</h3>
|
||||||
|
<p>Merb couldn't find an appropriate content_type to return,
|
||||||
|
based on what you said was available via provides() and
|
||||||
|
what the client requested.</p>
|
||||||
|
|
||||||
|
<h3>How to add a mime-type</h3>
|
||||||
|
<pre><code>
|
||||||
|
Merb.add_mime_type :pdf, :to_pdf, %w[application/pdf], "Content-Encoding" => "gzip"
|
||||||
|
</code></pre>
|
||||||
|
<h3>What this means is:</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Add a mime-type for :pdf</li>
|
||||||
|
<li>Register the method for converting objects to PDF as <code>#to_pdf</code>.</li>
|
||||||
|
<li>Register the incoming mime-type "Accept" header as <code>application/pdf</code>.</li>
|
||||||
|
<li>Specify a new header for PDF types so it will set <code>Content-Encoding</code> to gzip.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>You can then do:</h3>
|
||||||
|
<pre><code>
|
||||||
|
class Foo < Application
|
||||||
|
provides :pdf
|
||||||
|
end
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<h3>Where can I find help?</h3>
|
||||||
|
<p>If you have any questions or if you can't figure something out, please take a
|
||||||
|
look at our <a href="http://merbivore.com/"> project page</a>,
|
||||||
|
feel free to come chat at irc.freenode.net, channel #merb,
|
||||||
|
or post to <a href="http://groups.google.com/group/merb">merb mailing list</a>
|
||||||
|
on Google Groups.</p>
|
||||||
|
|
||||||
|
<h3>What if I've found a bug?</h3>
|
||||||
|
<p>If you want to file a bug or make your own contribution to Merb,
|
||||||
|
feel free to register and create a ticket at our
|
||||||
|
<a href="http://merb.lighthouseapp.com/">project development page</a>
|
||||||
|
on Lighthouse.</p>
|
||||||
|
|
||||||
|
<h3>How do I edit this page?</h3>
|
||||||
|
<p>You can change what people see when this happens by editing <tt>app/views/exceptions/not_acceptable.html.erb</tt>.</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="footer-container">
|
||||||
|
<hr />
|
||||||
|
<div class="left"></div>
|
||||||
|
<div class="right">© 2007 the merb dev team</div>
|
||||||
|
<p> </p>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<div id="container">
|
||||||
|
<div id="header-container">
|
||||||
|
<img src="/images/merb.jpg" />
|
||||||
|
<!-- <h1>Mongrel + Erb</h1> -->
|
||||||
|
<h2>pocket rocket web framework</h2>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="left-container">
|
||||||
|
<h3>Exception:</h3>
|
||||||
|
<p><%= params[:exception] %></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="main-container">
|
||||||
|
<h3>Welcome to Merb!</h3>
|
||||||
|
<p>Merb is a light-weight MVC framework written in Ruby. We hope you enjoy it.</p>
|
||||||
|
|
||||||
|
<h3>Where can I find help?</h3>
|
||||||
|
<p>If you have any questions or if you can't figure something out, please take a
|
||||||
|
look at our <a href="http://merbivore.com/"> project page</a>,
|
||||||
|
feel free to come chat at irc.freenode.net, channel #merb,
|
||||||
|
or post to <a href="http://groups.google.com/group/merb">merb mailing list</a>
|
||||||
|
on Google Groups.</p>
|
||||||
|
|
||||||
|
<h3>What if I've found a bug?</h3>
|
||||||
|
<p>If you want to file a bug or make your own contribution to Merb,
|
||||||
|
feel free to register and create a ticket at our
|
||||||
|
<a href="http://merb.lighthouseapp.com/">project development page</a>
|
||||||
|
on Lighthouse.</p>
|
||||||
|
|
||||||
|
<h3>How do I edit this page?</h3>
|
||||||
|
<p>You're seeing this page because you need to edit the following files:
|
||||||
|
<ul>
|
||||||
|
<li>config/router.rb <strong><em>(recommended)</em></strong></li>
|
||||||
|
<li>app/views/exceptions/not_found.html.erb <strong><em>(recommended)</em></strong></li>
|
||||||
|
<li>app/views/layout/application.html.erb <strong><em>(change this layout)</em></strong></li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="footer-container">
|
||||||
|
<hr />
|
||||||
|
<div class="left"></div>
|
||||||
|
<div class="right">© 2007 the merb dev team</div>
|
||||||
|
<p> </p>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1 @@
|
||||||
|
You're in index of the Favorites controller.
|
|
@ -0,0 +1,12 @@
|
||||||
|
%h1 Acceptable use policy
|
||||||
|
|
||||||
|
%p There are a few rules you must abide by to use this site. Violation will at least mean a permenant ban and at worst prosecution under federal law.
|
||||||
|
|
||||||
|
%ol
|
||||||
|
%li You must be 18 years old.
|
||||||
|
%li The pictures must be of a person. One <em>consenting adult</em> person.
|
||||||
|
%li Group pictures are allowed on the grounds that you are voting for <strong>the entire group</strong>.
|
||||||
|
%li Nudity is not allowed. Partial nudity is allowed. We will use any discretionary method to determine what is allowed. Be warned.
|
||||||
|
%li Always offer to send a statistics link to the person you are photographing. It's just polite :)
|
||||||
|
|
||||||
|
%p We will <strong>never</strong> share any personal information gathered on this site. The only information we gather is so that <em>you</em>, the user, will have access to the data later.
|
|
@ -0,0 +1,61 @@
|
||||||
|
%style{ :type => 'text/css' }
|
||||||
|
:sass
|
||||||
|
#front_page
|
||||||
|
blockquote
|
||||||
|
:font-size 16px
|
||||||
|
:padding 5px 0px 5px 15px
|
||||||
|
:background-color #eeeeee
|
||||||
|
:border-top 1px dashed #9f9f9f
|
||||||
|
:border-right 1px dashed #9f9f9f
|
||||||
|
:border-bottom 1px solid #d4d4d4
|
||||||
|
:border-left 1px solid #d4d4d4
|
||||||
|
em
|
||||||
|
:margin-left 20px
|
||||||
|
h1
|
||||||
|
:text-align center
|
||||||
|
|
||||||
|
%span#front_page
|
||||||
|
%h1 What this is all about
|
||||||
|
|
||||||
|
%blockquote
|
||||||
|
All you young guys are on a binary system. It's either <tt>0</tt> or <tt>1</tt>.
|
||||||
|
%br
|
||||||
|
%em Larry Bell
|
||||||
|
|
||||||
|
%blockquote
|
||||||
|
All you old guys are on the analog system. Join the digital revolution.
|
||||||
|
%br
|
||||||
|
%em Ross Bagwell
|
||||||
|
|
||||||
|
%h1 Getting started
|
||||||
|
|
||||||
|
%ul.no_list_style
|
||||||
|
- if logged_in?
|
||||||
|
%li
|
||||||
|
%a{ :href => url(:edit_user, :id => current_user.user_name) }
|
||||||
|
%img{ :src => '/images/system-lock-screen.png' }
|
||||||
|
Change your password
|
||||||
|
%li
|
||||||
|
%a{ :href => url(:favorite, :id => current_user.user_name) }
|
||||||
|
%img{ :src => '/images/emblem-favorite.png' }
|
||||||
|
Check your favorites
|
||||||
|
%li
|
||||||
|
%a{ :href => url(:new_photo) }
|
||||||
|
%img{ :src => '/images/camera-photo.png' }
|
||||||
|
Upload photos
|
||||||
|
%li
|
||||||
|
%a{ :href => url(:votes) }
|
||||||
|
%img{ :src => '/images/vote.png' }
|
||||||
|
Vote on new photos
|
||||||
|
%li
|
||||||
|
%a{ :href => url(:stat, :id => current_user.user_name) }
|
||||||
|
%img{ :src => '/images/utilities-system-monitor.png' }
|
||||||
|
Check stats on photos of yourself
|
||||||
|
- else
|
||||||
|
%li <img src='/images/system-users.png' /> Sign up for an account
|
||||||
|
%li <img src='/images/system-lock-screen.png' /> Log in if you have one
|
||||||
|
%li <img src='/images/emblem-favorite.png' /> Check your favorites
|
||||||
|
%li <img src='/images/camera-photo.png' /> Upload photos
|
||||||
|
%li <img src='/images/vote.png' /> Vote on new photos
|
||||||
|
%li <img src='/images/utilities-system-monitor.png' /> Check stats on photos of yourself
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
!!! Strict
|
||||||
|
%html{html_attrs{'en-us'}}
|
||||||
|
%head
|
||||||
|
%title Binary Attraction
|
||||||
|
%meta{ 'http-equiv' => "content-type", :content => "text/html; charset=utf8" }
|
||||||
|
%link{ :href => "/stylesheets/ba.css", :rel => "stylesheet", :type => "text/css", :media => "screen", :charset => "utf-8" }
|
||||||
|
%script{ :src => "/javascripts/prototype.js", :type => "text/javascript" }
|
||||||
|
%script{ :src => "/javascripts/effects.js", :type => "text/javascript" }
|
||||||
|
%script{ :src => "/javascripts/dragdrop.js", :type => "text/javascript" }
|
||||||
|
%script{ :src => "/javascripts/application.js", :type => "text/javascript" }
|
||||||
|
- unless flash.keys.empty?
|
||||||
|
:javascript
|
||||||
|
hide_flashes();
|
||||||
|
%body
|
||||||
|
#container
|
||||||
|
- unless flash.keys.empty?
|
||||||
|
#flash_container
|
||||||
|
- flash.keys.each do |key|
|
||||||
|
%div{ :class => key }= flash[key]
|
||||||
|
#header
|
||||||
|
%span#header_image
|
||||||
|
%a{ :href => '/', :title => 'B.A. Home' }
|
||||||
|
%img{ :src => '/images/binaryattraction.png', :alt => 'Binary Attraction' }
|
||||||
|
#tool_bar
|
||||||
|
- if logged_in?
|
||||||
|
%a{ :href => url(:new_vote), :title => 'Vote' }
|
||||||
|
%img{ :src => '/images/vote.png' }
|
||||||
|
Vote
|
||||||
|
|
|
||||||
|
%a{ :href => url(:new_photo), :title => 'Upload a photo' }
|
||||||
|
%img{ :src => '/images/camera-photo.png' }
|
||||||
|
Upload a photo
|
||||||
|
|
|
||||||
|
%a{ :href => url(:favorite, :id => session[:user_id]), :title => 'Favorites' }
|
||||||
|
%img{ :src => '/images/emblem-favorite.png' }
|
||||||
|
Favorites
|
||||||
|
|
|
||||||
|
%a{ :href => url(:stats), :title => 'Stats' }
|
||||||
|
%img{ :src => '/images/utilities-system-monitor.png' }
|
||||||
|
Check Stats
|
||||||
|
|
|
||||||
|
%a{ :href => url(:delete_session, :id => session[:user_id]), :title => 'Log out' }
|
||||||
|
%img{ :src => '/images/system-log-out.png' }
|
||||||
|
Log out
|
||||||
|
- else
|
||||||
|
%a{ :href => url(:new_user), :title => 'Sign Up' }
|
||||||
|
%img{ :src => '/images/system-users.png' }
|
||||||
|
Sign Up
|
||||||
|
|
|
||||||
|
%a{ :href => url(:new_session), :title => 'Log In' }
|
||||||
|
%img{ :src => '/images/system-lock-screen.png' }
|
||||||
|
Log In
|
||||||
|
#content
|
||||||
|
= catch_content :for_layout
|
||||||
|
#footer
|
||||||
|
© 2008
|
||||||
|
%a{ :href => 'http://penguincoder.org' } penguincoder
|
||||||
|
| Usage of this site requires
|
||||||
|
%a{ :href => '/acceptable_use' } acceptable use policy
|
|
@ -0,0 +1 @@
|
||||||
|
You're in index of the Photos controller.
|
|
@ -0,0 +1,12 @@
|
||||||
|
= form :action => url(:sessions) do
|
||||||
|
%fieldset
|
||||||
|
%p
|
||||||
|
%label{ :for => 'user_name' }
|
||||||
|
User Name
|
||||||
|
= text_field :name => 'user_name', :id => 'user_name'
|
||||||
|
%p
|
||||||
|
%label{ :for => 'password' }
|
||||||
|
Password
|
||||||
|
= password_field :name => 'password', :id => 'password'
|
||||||
|
= submit 'Login'
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
You're in index of the Stats controller.
|
|
@ -0,0 +1,12 @@
|
||||||
|
= error_messages @user
|
||||||
|
|
||||||
|
= form :action => url(:edit_user, :id => @user.user_name) do
|
||||||
|
%fieldset
|
||||||
|
%legend== Changing settings for user <em>#{@user.user_name}</em>
|
||||||
|
%p
|
||||||
|
%label{ :for => 'password' } Password
|
||||||
|
= password_field :name => 'user[password]', :id => 'password'
|
||||||
|
%p
|
||||||
|
%label{ :for => 'password_confirmation' } Confirm
|
||||||
|
= password_field :name => 'user[password]', :id => 'password'
|
||||||
|
= submit 'Save'
|
|
@ -0,0 +1,9 @@
|
||||||
|
%h1 Peoples.
|
||||||
|
|
||||||
|
%ul
|
||||||
|
- @users.each do |user|
|
||||||
|
%li
|
||||||
|
%a{ :href => url(:edit_user, :id => user.user_name) }= user.user_name
|
||||||
|
%a{ :href => url(:delete_user, :id => user.user_name), :onclick => "return confirm('Are you sure?');" }
|
||||||
|
%img{ :src => '/images/user-trash.png' }
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
= error_messages @user
|
||||||
|
|
||||||
|
= form_for @user do
|
||||||
|
%fieldset
|
||||||
|
%legend Create a new user
|
||||||
|
%p
|
||||||
|
%label{ :for => 'user_name' } User Name
|
||||||
|
= text_field :name => 'user[user_name]', :id => 'user_name'
|
||||||
|
%p
|
||||||
|
%label{ :for => 'password' } Password
|
||||||
|
= password_field :name => 'user[password]', :id => 'password'
|
||||||
|
%p
|
||||||
|
%label{ :for => 'password_confirmation' } Confirm
|
||||||
|
= password_field :name => 'user[password_confirmation]', :id => 'password_confirmation'
|
||||||
|
%p
|
||||||
|
%label{ :for => 'recaptcha_response_field' } Captcha
|
||||||
|
#recaptcha_container= recaptcha_tags
|
||||||
|
= submit 'Create'
|
|
@ -0,0 +1 @@
|
||||||
|
You're in index of the Votes controller.
|
|
@ -0,0 +1 @@
|
||||||
|
Autotest.add_discovery { "merb" }
|
|
@ -0,0 +1,149 @@
|
||||||
|
# Adapted from Autotest::Rails
|
||||||
|
require 'autotest'
|
||||||
|
|
||||||
|
class Autotest::Merb < Autotest
|
||||||
|
|
||||||
|
# +model_tests_dir+:: the directory to find model-centric tests
|
||||||
|
# +controller_tests_dir+:: the directory to find controller-centric tests
|
||||||
|
# +view_tests_dir+:: the directory to find view-centric tests
|
||||||
|
# +fixtures_dir+:: the directory to find fixtures in
|
||||||
|
attr_accessor :model_tests_dir, :controller_tests_dir, :view_tests_dir, :fixtures_dir
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
super
|
||||||
|
|
||||||
|
initialize_test_layout
|
||||||
|
|
||||||
|
# Ignore any happenings in these directories
|
||||||
|
add_exception %r%^\./(?:doc|log|public|tmp)%
|
||||||
|
|
||||||
|
# Ignore any mappings that Autotest may have already set up
|
||||||
|
clear_mappings
|
||||||
|
|
||||||
|
# Any changes to a file in the root of the 'lib' directory will run any
|
||||||
|
# model test with a corresponding name.
|
||||||
|
add_mapping %r%^lib\/.*\.rb% do |filename, _|
|
||||||
|
files_matching Regexp.new(["^#{model_test_for(filename)}$"])
|
||||||
|
end
|
||||||
|
|
||||||
|
# Any changes to a fixture will run corresponding view, controller and
|
||||||
|
# model tests
|
||||||
|
add_mapping %r%^#{fixtures_dir}/(.*)s.yml% do |_, m|
|
||||||
|
[
|
||||||
|
model_test_for(m[1]),
|
||||||
|
controller_test_for(m[1]),
|
||||||
|
view_test_for(m[1])
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Any change to a test or test will cause it to be run
|
||||||
|
add_mapping %r%^test/(unit|models|integration|controllers|views|functional)/.*rb$% do |filename, _|
|
||||||
|
filename
|
||||||
|
end
|
||||||
|
|
||||||
|
# Any change to a model will cause it's corresponding test to be run
|
||||||
|
add_mapping %r%^app/models/(.*)\.rb$% do |_, m|
|
||||||
|
model_test_for(m[1])
|
||||||
|
end
|
||||||
|
|
||||||
|
# Any change to the global helper will result in all view and controller
|
||||||
|
# tests being run
|
||||||
|
add_mapping %r%^app/helpers/global_helpers.rb% do
|
||||||
|
files_matching %r%^test/(views|functional|controllers)/.*_test\.rb$%
|
||||||
|
end
|
||||||
|
|
||||||
|
# Any change to a helper will run it's corresponding view and controller
|
||||||
|
# tests, unless the helper is the global helper. Changes to the global
|
||||||
|
# helper run all view and controller tests.
|
||||||
|
add_mapping %r%^app/helpers/(.*)_helper(s)?.rb% do |_, m|
|
||||||
|
if m[1] == "global" then
|
||||||
|
files_matching %r%^test/(views|functional|controllers)/.*_test\.rb$%
|
||||||
|
else
|
||||||
|
[
|
||||||
|
view_test_for(m[1]),
|
||||||
|
controller_test_for(m[1])
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Changes to views result in their corresponding view and controller test
|
||||||
|
# being run
|
||||||
|
add_mapping %r%^app/views/(.*)/% do |_, m|
|
||||||
|
[
|
||||||
|
view_test_for(m[1]),
|
||||||
|
controller_test_for(m[1])
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Changes to a controller result in its corresponding test being run. If
|
||||||
|
# the controller is the exception or application controller, all
|
||||||
|
# controller tests are run.
|
||||||
|
add_mapping %r%^app/controllers/(.*)\.rb$% do |_, m|
|
||||||
|
if ["application", "exception"].include?(m[1])
|
||||||
|
files_matching %r%^test/(controllers|views|functional)/.*_test\.rb$%
|
||||||
|
else
|
||||||
|
controller_test_for(m[1])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# If a change is made to the router, run all controller and view tests
|
||||||
|
add_mapping %r%^config/router.rb$% do # FIX
|
||||||
|
files_matching %r%^test/(controllers|views|functional)/.*_test\.rb$%
|
||||||
|
end
|
||||||
|
|
||||||
|
# If any of the major files governing the environment are altered, run
|
||||||
|
# everything
|
||||||
|
add_mapping %r%^test/test_helper.rb|config/(init|rack|environments/test.rb|database.yml)% do # FIX
|
||||||
|
files_matching %r%^test/(unit|models|controllers|views|functional)/.*_test\.rb$%
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Determines the paths we can expect tests or specs to reside, as well as
|
||||||
|
# corresponding fixtures.
|
||||||
|
def initialize_test_layout
|
||||||
|
self.model_tests_dir = "test/unit"
|
||||||
|
self.controller_tests_dir = "test/functional"
|
||||||
|
self.view_tests_dir = "test/views"
|
||||||
|
self.fixtures_dir = "test/fixtures"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Given a filename and the test type, this method will return the
|
||||||
|
# corresponding test's or spec's name.
|
||||||
|
#
|
||||||
|
# ==== Arguments
|
||||||
|
# +filename+<String>:: the file name of the model, view, or controller
|
||||||
|
# +kind_of_test+<Symbol>:: the type of test we that we should run
|
||||||
|
#
|
||||||
|
# ==== Returns
|
||||||
|
# String:: the name of the corresponding test or spec
|
||||||
|
#
|
||||||
|
# ==== Example
|
||||||
|
#
|
||||||
|
# > test_for("user", :model)
|
||||||
|
# => "user_test.rb"
|
||||||
|
# > test_for("login", :controller)
|
||||||
|
# => "login_controller_test.rb"
|
||||||
|
# > test_for("form", :view)
|
||||||
|
# => "form_view_spec.rb" # If you're running a RSpec-like suite
|
||||||
|
def test_for(filename, kind_of_test)
|
||||||
|
name = [filename]
|
||||||
|
name << kind_of_test.to_s if kind_of_test == :view
|
||||||
|
name << "test"
|
||||||
|
return name.join("_") + ".rb"
|
||||||
|
end
|
||||||
|
|
||||||
|
def model_test_for(filename)
|
||||||
|
[model_tests_dir, test_for(filename, :model)].join("/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def controller_test_for(filename)
|
||||||
|
[controller_tests_dir, test_for(filename, :controller)].join("/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def view_test_for(filename)
|
||||||
|
[view_tests_dir, test_for(filename, :view)].join("/")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,165 @@
|
||||||
|
# Adapted from Autotest::Rails, RSpec's autotest class, as well as merb-core's.
|
||||||
|
require 'autotest'
|
||||||
|
|
||||||
|
class RspecCommandError < StandardError; end
|
||||||
|
|
||||||
|
# This class maps your application's structure so Autotest can understand what
|
||||||
|
# specs to run when files change.
|
||||||
|
#
|
||||||
|
# Fixtures are _not_ covered by this class. If you change a fixture file, you
|
||||||
|
# will have to run your spec suite manually, or, better yet, provide your own
|
||||||
|
# Autotest map explaining how your fixtures are set up.
|
||||||
|
class Autotest::MerbRspec < Autotest
|
||||||
|
def initialize
|
||||||
|
super
|
||||||
|
|
||||||
|
# Ignore any happenings in these directories
|
||||||
|
add_exception %r%^\./(?:doc|log|public|tmp|\.git|\.hg|\.svn|framework|gems|schema|\.DS_Store|autotest|bin|.*\.sqlite3)%
|
||||||
|
# Ignore SCM directories and custom Autotest mappings
|
||||||
|
%w[.svn .hg .git .autotest].each { |exception| add_exception(exception) }
|
||||||
|
|
||||||
|
# Ignore any mappings that Autotest may have already set up
|
||||||
|
clear_mappings
|
||||||
|
|
||||||
|
# Anything in /lib could have a spec anywhere, if at all. So, look for
|
||||||
|
# files with roughly the same name as the file in /lib
|
||||||
|
add_mapping %r%^lib\/(.*)\.rb% do |_, m|
|
||||||
|
files_matching %r%^spec\/#{m[1]}%
|
||||||
|
end
|
||||||
|
|
||||||
|
add_mapping %r%^spec/(spec_helper|shared/.*)\.rb$% do
|
||||||
|
all_specs
|
||||||
|
end
|
||||||
|
|
||||||
|
# Changing a spec will cause it to run itself
|
||||||
|
add_mapping %r%^spec/.*\.rb$% do |filename, _|
|
||||||
|
filename
|
||||||
|
end
|
||||||
|
|
||||||
|
# Any change to a model will cause it's corresponding test to be run
|
||||||
|
add_mapping %r%^app/models/(.*)\.rb$% do |_, m|
|
||||||
|
spec_for(m[1], 'model')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Any change to global_helpers will result in all view and controller
|
||||||
|
# tests being run
|
||||||
|
add_mapping %r%^app/helpers/global_helpers\.rb% do
|
||||||
|
files_matching %r%^spec/(views|controllers|helpers)/.*_spec\.rb$%
|
||||||
|
end
|
||||||
|
|
||||||
|
# Any change to a helper will cause its spec to be run
|
||||||
|
add_mapping %r%^app/helpers/((.*)_helper(s)?)\.rb% do |_, m|
|
||||||
|
spec_for(m[1], 'helper')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Changes to a view cause its spec to be run
|
||||||
|
add_mapping %r%^app/views/(.*)/% do |_, m|
|
||||||
|
spec_for(m[1], 'view')
|
||||||
|
end
|
||||||
|
|
||||||
|
# Changes to a controller result in its corresponding spec being run. If
|
||||||
|
# the controller is the exception or application controller, all
|
||||||
|
# controller specs are run.
|
||||||
|
add_mapping %r%^app/controllers/(.*)\.rb$% do |_, m|
|
||||||
|
if ["application", "exception"].include?(m[1])
|
||||||
|
files_matching %r%^spec/controllers/.*_spec\.rb$%
|
||||||
|
else
|
||||||
|
spec_for(m[1], 'controller')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# If a change is made to the router, run controller, view and helper specs
|
||||||
|
add_mapping %r%^config/router.rb$% do
|
||||||
|
files_matching %r%^spec/(controllers|views|helpers)/.*_spec\.rb$%
|
||||||
|
end
|
||||||
|
|
||||||
|
# If any of the major files governing the environment are altered, run
|
||||||
|
# everything
|
||||||
|
add_mapping %r%^config/(init|rack|environments/test).*\.rb|database\.yml% do
|
||||||
|
all_specs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def failed_results(results)
|
||||||
|
results.scan(/^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m)
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_results(results)
|
||||||
|
@failures = failed_results(results)
|
||||||
|
@files_to_test = consolidate_failures(@failures)
|
||||||
|
@files_to_test.empty? && !$TESTING ? hook(:green) : hook(:red)
|
||||||
|
@tainted = !@files_to_test.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def consolidate_failures(failed)
|
||||||
|
filters = Hash.new { |h,k| h[k] = [] }
|
||||||
|
failed.each do |spec, failed_trace|
|
||||||
|
if f = test_files_for(failed).find { |f| f =~ /spec\// }
|
||||||
|
filters[f] << spec
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
filters
|
||||||
|
end
|
||||||
|
|
||||||
|
def make_test_cmd(specs_to_runs)
|
||||||
|
[
|
||||||
|
ruby,
|
||||||
|
"-S",
|
||||||
|
spec_command,
|
||||||
|
add_options_if_present,
|
||||||
|
files_to_test.keys.flatten.join(' ')
|
||||||
|
].join(' ')
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_options_if_present
|
||||||
|
File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : ""
|
||||||
|
end
|
||||||
|
|
||||||
|
# Finds the proper spec command to use. Precendence is set in the
|
||||||
|
# lazily-evaluated method spec_commands. Alias + Override that in
|
||||||
|
# ~/.autotest to provide a different spec command then the default
|
||||||
|
# paths provided.
|
||||||
|
def spec_command(separator=File::ALT_SEPARATOR)
|
||||||
|
unless defined?(@spec_command)
|
||||||
|
@spec_command = spec_commands.find { |cmd| File.exists?(cmd) }
|
||||||
|
|
||||||
|
raise RspecCommandError, "No spec command could be found" unless @spec_command
|
||||||
|
|
||||||
|
@spec_command.gsub!(File::SEPARATOR, separator) if separator
|
||||||
|
end
|
||||||
|
@spec_command
|
||||||
|
end
|
||||||
|
|
||||||
|
# Autotest will look for spec commands in the following
|
||||||
|
# locations, in this order:
|
||||||
|
#
|
||||||
|
# * default spec bin/loader installed in Rubygems
|
||||||
|
# * any spec command found in PATH
|
||||||
|
def spec_commands
|
||||||
|
[File.join(Config::CONFIG['bindir'], 'spec'), 'spec']
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Runs +files_matching+ for all specs
|
||||||
|
def all_specs
|
||||||
|
files_matching %r%^spec/.*_spec\.rb$%
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generates a path to some spec given its kind and the match from a mapping
|
||||||
|
#
|
||||||
|
# ==== Arguments
|
||||||
|
# match<String>:: the match from a mapping
|
||||||
|
# kind<String>:: the kind of spec that the match represents
|
||||||
|
#
|
||||||
|
# ==== Returns
|
||||||
|
# String
|
||||||
|
#
|
||||||
|
# ==== Example
|
||||||
|
# > spec_for('post', :view')
|
||||||
|
# => "spec/views/post_spec.rb"
|
||||||
|
def spec_for(match, kind)
|
||||||
|
File.join("spec", kind + 's', "#{match}_spec.rb")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
Merb.logger.info("Loaded DEVELOPMENT Environment...")
|
||||||
|
Merb::Config.use { |c|
|
||||||
|
c[:exception_details] = true
|
||||||
|
c[:reload_classes] = true
|
||||||
|
c[:reload_time] = 0.5
|
||||||
|
c[:log_auto_flush ] = true
|
||||||
|
c[:log_level] = :debug
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
Merb.logger.info("Loaded PRODUCTION Environment...")
|
||||||
|
Merb::Config.use { |c|
|
||||||
|
c[:exception_details] = false
|
||||||
|
c[:reload_classes] = false
|
||||||
|
c[:log_level] = :error
|
||||||
|
c[:log_file] = Merb.log_path + "/production.log"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
Merb.logger.info("Loaded RAKE Environment...")
|
||||||
|
Merb::Config.use { |c|
|
||||||
|
c[:exception_details] = true
|
||||||
|
c[:reload_classes] = false
|
||||||
|
c[:log_auto_flush ] = true
|
||||||
|
c[:log_file] = Merb.log_path / 'merb_rake.log'
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
Merb.logger.info("Loaded TEST Environment...")
|
||||||
|
Merb::Config.use { |c|
|
||||||
|
c[:testing] = true
|
||||||
|
c[:exception_details] = true
|
||||||
|
c[:log_auto_flush ] = true
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
Gem.clear_paths
|
||||||
|
Gem.path.unshift(Merb.root / "gems")
|
||||||
|
$LOAD_PATH.unshift(Merb.root / "lib")
|
||||||
|
# Merb.push_path(:lib, Merb.root / "lib") # uses **/*.rb as path glob
|
||||||
|
|
||||||
|
dependencies 'haml', 'sass', 'merb_helpers', 'merb_has_flash', 'digest/sha1', 'recaptcha'
|
||||||
|
|
||||||
|
Merb::BootLoader.after_app_loads do
|
||||||
|
recaptcha_path = File.join(Merb.root, 'config', 'recaptcha.yml')
|
||||||
|
if File.file?(recaptcha_path) and File.readable?(recaptcha_path)
|
||||||
|
rc = YAML::load_file(recaptcha_path)
|
||||||
|
ENV['RECAPTCHA_PUBLIC_KEY'] = rc[:public]
|
||||||
|
ENV['RECAPTCHA_PRIVATE_KEY'] = rc[:private]
|
||||||
|
else
|
||||||
|
raise "ReCaptcha configuration file not found!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
use_orm :activerecord
|
||||||
|
use_test :rspec
|
||||||
|
use_template_engine :haml
|
||||||
|
Merb::Config.use do |c|
|
||||||
|
c[:session_secret_key] = 'ccf75249b0efbdb3edff96d0a1b16b19cf91f31e'
|
||||||
|
c[:session_store] = :activerecord
|
||||||
|
c[:sass] ||= {}
|
||||||
|
c[:sass][:style] = :compact
|
||||||
|
end
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
# use PathPrefix Middleware if :path_prefix is set in Merb::Config
|
||||||
|
if prefix = ::Merb::Config[:path_prefix]
|
||||||
|
use Merb::Rack::PathPrefix, prefix
|
||||||
|
end
|
||||||
|
|
||||||
|
# comment this out if you are running merb behind a load balancer
|
||||||
|
# that serves static files
|
||||||
|
use Merb::Rack::Static, Merb.dir_for(:public)
|
||||||
|
|
||||||
|
# this is our main merb application
|
||||||
|
run Merb::Rack::Application.new
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
:public: PUBKEY
|
||||||
|
:private: PRIVKEY
|
|
@ -0,0 +1,13 @@
|
||||||
|
Merb.logger.info("Compiling routes...")
|
||||||
|
Merb::Router.prepare do |r|
|
||||||
|
r.resources :sessions
|
||||||
|
r.resources :users
|
||||||
|
r.resources :people
|
||||||
|
r.resources :votes
|
||||||
|
r.resources :photos
|
||||||
|
r.resources :favorites
|
||||||
|
r.resources :stats
|
||||||
|
# r.default_routes
|
||||||
|
r.match('/').to( :controller => 'home', :action => 'index' )
|
||||||
|
r.match('/acceptable_use').to( :controller => 'home', :action => 'acceptable_use' )
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2007 Jason L Perry
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,78 @@
|
||||||
|
# ReCAPTCHA
|
||||||
|
module Ambethia
|
||||||
|
module ReCaptcha
|
||||||
|
RECAPTCHA_API_SERVER = 'http://api.recaptcha.net';
|
||||||
|
RECAPTCHA_API_SECURE_SERVER = 'https://api-secure.recaptcha.net';
|
||||||
|
RECAPTCHA_VERIFY_SERVER = 'api-verify.recaptcha.net';
|
||||||
|
|
||||||
|
SKIP_VERIFY_ENV = ['test']
|
||||||
|
|
||||||
|
module Helper
|
||||||
|
# Your public API can be specified in the +options+ hash or preferably the environment
|
||||||
|
# variable +RECAPTCHA_PUBLIC_KEY+.
|
||||||
|
def recaptcha_tags(options = {})
|
||||||
|
# Default options
|
||||||
|
key = options[:public_key] ||= ENV['RECAPTCHA_PUBLIC_KEY']
|
||||||
|
error = options[:error] ||= session[:recaptcha_error]
|
||||||
|
uri = options[:ssl] ? RECAPTCHA_API_SECURE_SERVER : RECAPTCHA_API_SERVER
|
||||||
|
xhtml = Builder::XmlMarkup.new :target => out=(''), :indent => 2 # Because I can.
|
||||||
|
if options[:display]
|
||||||
|
xhtml.script(:type => "text/javascript"){ xhtml.text! "var RecaptchaOptions = #{options[:display].to_json};\n"}
|
||||||
|
end
|
||||||
|
if options[:ajax]
|
||||||
|
xhtml.div(:id => 'dynamic_recaptcha') {}
|
||||||
|
xhtml.script(:type => "text/javascript", :src => "#{uri}/js/recaptcha_ajax.js") {}
|
||||||
|
xhtml.script(:type => "text/javascript") do
|
||||||
|
xhtml.text! "Recaptcha.create('#{key}', document.getElementById('dynamic_recaptcha') );"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
xhtml.script(:type => "text/javascript", :src => "#{uri}/challenge?k=#{key}&error=#{error}") {}
|
||||||
|
unless options[:noscript] == false
|
||||||
|
xhtml.noscript do
|
||||||
|
xhtml.iframe(:src => "#{uri}/noscript?k=#{key}",
|
||||||
|
:height => options[:iframe_height] ||= 300,
|
||||||
|
:width => options[:iframe_width] ||= 500,
|
||||||
|
:frameborder => 0) {}; xhtml.br
|
||||||
|
xhtml.textarea(:name => "recaptcha_challenge_field", :rows => 3, :cols => 40) {}
|
||||||
|
xhtml.input :name => "recaptcha_response_field",
|
||||||
|
:type => "hidden", :value => "manual_challenge"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
raise ReCaptchaError, "No public key specified." unless key
|
||||||
|
return out
|
||||||
|
end # recaptcha_tags
|
||||||
|
end # Helpers
|
||||||
|
|
||||||
|
module Controller
|
||||||
|
# Your private API key must be specified in the environment variable +RECAPTCHA_PRIVATE_KEY+
|
||||||
|
def verify_recaptcha(model = nil)
|
||||||
|
return true if SKIP_VERIFY_ENV.include? ENV['RAILS_ENV']
|
||||||
|
raise ReCaptchaError, "No private key specified." unless ENV['RECAPTCHA_PRIVATE_KEY']
|
||||||
|
begin
|
||||||
|
recaptcha = Net::HTTP.post_form URI.parse("http://#{RECAPTCHA_VERIFY_SERVER}/verify"), {
|
||||||
|
:privatekey => ENV['RECAPTCHA_PRIVATE_KEY'],
|
||||||
|
:remoteip => request.remote_ip,
|
||||||
|
:challenge => params[:recaptcha_challenge_field],
|
||||||
|
:response => params[:recaptcha_response_field]
|
||||||
|
}
|
||||||
|
answer, error = recaptcha.body.split.map { |s| s.chomp }
|
||||||
|
unless answer == 'true'
|
||||||
|
session[:recaptcha_error] = error
|
||||||
|
model.valid? if model
|
||||||
|
model.errors.add_to_base "Captcha response is incorrect, please try again." if model
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
session[:recaptcha_error] = nil
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
rescue Exception => e
|
||||||
|
raise ReCaptchaError, e
|
||||||
|
end
|
||||||
|
end # verify_recaptcha
|
||||||
|
end # ControllerHelpers
|
||||||
|
|
||||||
|
class ReCaptchaError < StandardError; end
|
||||||
|
|
||||||
|
end # ReCaptcha
|
||||||
|
end # Ambethia
|
|
@ -0,0 +1,822 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
require 'rubygems'
|
||||||
|
require 'rubygems/dependency_installer'
|
||||||
|
require 'rubygems/uninstaller'
|
||||||
|
require 'thor'
|
||||||
|
require 'fileutils'
|
||||||
|
require 'yaml'
|
||||||
|
|
||||||
|
module MerbThorHelper
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# The current working directory, or Merb app root (--merb-root option).
|
||||||
|
def working_dir
|
||||||
|
@_working_dir ||= File.expand_path(options['merb-root'] || Dir.pwd)
|
||||||
|
end
|
||||||
|
|
||||||
|
# We should have a ./src dir for local and system-wide management.
|
||||||
|
def source_dir
|
||||||
|
@_source_dir ||= File.join(working_dir, 'src')
|
||||||
|
create_if_missing(@_source_dir)
|
||||||
|
@_source_dir
|
||||||
|
end
|
||||||
|
|
||||||
|
# If a local ./gems dir is found, it means we are in a Merb app.
|
||||||
|
def application?
|
||||||
|
gem_dir
|
||||||
|
end
|
||||||
|
|
||||||
|
# If a local ./gems dir is found, return it.
|
||||||
|
def gem_dir
|
||||||
|
if File.directory?(dir = File.join(working_dir, 'gems'))
|
||||||
|
dir
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# If we're in a Merb app, we can have a ./bin directory;
|
||||||
|
# create it if it's not there.
|
||||||
|
def bin_dir
|
||||||
|
@_bin_dir ||= begin
|
||||||
|
if gem_dir
|
||||||
|
dir = File.join(working_dir, 'bin')
|
||||||
|
create_if_missing(dir)
|
||||||
|
dir
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helper to create dir unless it exists.
|
||||||
|
def create_if_missing(path)
|
||||||
|
FileUtils.mkdir(path) unless File.exists?(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a modified executable wrapper in the app's ./bin directory.
|
||||||
|
def ensure_local_bin_for(*gems)
|
||||||
|
if bin_dir && File.directory?(bin_dir)
|
||||||
|
gems.each do |gem|
|
||||||
|
if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{gem}-*.gemspec")].last
|
||||||
|
spec = Gem::Specification.load(gemspec_path)
|
||||||
|
spec.executables.each do |exec|
|
||||||
|
if File.exists?(executable = File.join(gem_dir, 'bin', exec))
|
||||||
|
local_executable = File.join(bin_dir, exec)
|
||||||
|
puts "Adding local executable #{local_executable}"
|
||||||
|
File.open(local_executable, 'w', 0755) do |f|
|
||||||
|
f.write(executable_wrapper(spec, exec))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def executable_wrapper(spec, bin_file_name)
|
||||||
|
<<-TEXT
|
||||||
|
#!/usr/bin/env #{RbConfig::CONFIG["ruby_install_name"]}
|
||||||
|
#
|
||||||
|
# This file was generated by merb.thor.
|
||||||
|
#
|
||||||
|
# The application '#{spec.name}' is installed as part of a gem, and
|
||||||
|
# this file is here to facilitate running it.
|
||||||
|
#
|
||||||
|
|
||||||
|
require 'rubygems'
|
||||||
|
|
||||||
|
if File.directory?(gems_dir = File.join(File.dirname(__FILE__), '..', 'gems'))
|
||||||
|
$BUNDLE = true; Gem.clear_paths; Gem.path.unshift(gems_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
version = "#{Gem::Requirement.default}"
|
||||||
|
|
||||||
|
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
|
||||||
|
version = $1
|
||||||
|
ARGV.shift
|
||||||
|
end
|
||||||
|
|
||||||
|
gem '#{spec.name}', version
|
||||||
|
load '#{bin_file_name}'
|
||||||
|
TEXT
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# - a task to figure out an app's dependencies
|
||||||
|
# - pulling a specific UUID/Tag (gitspec hash) with clone/update
|
||||||
|
# - a 'deploy' task (in addition to 'redeploy' ?)
|
||||||
|
# - eventually take a --orm option for the 'merb-stack' type of tasks
|
||||||
|
|
||||||
|
class Merb < Thor
|
||||||
|
|
||||||
|
class SourcePathMissing < Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
class GemPathMissing < Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
class GemInstallError < Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
class GemUninstallError < Exception
|
||||||
|
end
|
||||||
|
|
||||||
|
# Install a Merb stack from stable RubyForge gems. Optionally install a
|
||||||
|
# suitable Rack adapter/server when setting --adapter to one of the
|
||||||
|
# following: mongrel, emongrel, thin or ebb.
|
||||||
|
|
||||||
|
desc 'stable', 'Install extlib, merb-core and merb-more from rubygems'
|
||||||
|
method_options "--merb-root" => :optional,
|
||||||
|
"--adapter" => :optional
|
||||||
|
def stable
|
||||||
|
adapters = %w[mongrel emongrel thin ebb]
|
||||||
|
stable = Stable.new
|
||||||
|
stable.options = options
|
||||||
|
if stable.core && stable.more
|
||||||
|
puts "Installed extlib, merb-core and merb-more"
|
||||||
|
if options[:adapter] && adapters.include?(options[:adapter]) &&
|
||||||
|
stable.refresh_from_gems(options[:adapter])
|
||||||
|
puts "Installed #{options[:adapter]}"
|
||||||
|
elsif options[:adapter]
|
||||||
|
puts "Please specify one of the following adapters: #{adapters.join(' ')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Stable < Thor
|
||||||
|
|
||||||
|
# The Stable tasks deal with known -stable- gems; available
|
||||||
|
# as shortcuts to Merb and DataMapper gems.
|
||||||
|
#
|
||||||
|
# These are pulled from rubyforge and installed into into the
|
||||||
|
# desired gems dir (either system-wide or into the application's
|
||||||
|
# gems directory).
|
||||||
|
|
||||||
|
include MerbThorHelper
|
||||||
|
|
||||||
|
# Gets latest gem versions from RubyForge and installs them.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# thor merb:edge:core
|
||||||
|
# thor merb:edge:core --merb-root ./path/to/your/app
|
||||||
|
# thor merb:edge:core --sources ./path/to/sources.yml
|
||||||
|
|
||||||
|
desc 'core', 'Install extlib and merb-core from git HEAD'
|
||||||
|
method_options "--merb-root" => :optional
|
||||||
|
def core
|
||||||
|
refresh_from_gems 'extlib', 'merb-core'
|
||||||
|
ensure_local_bin_for('merb-core', 'rake', 'rspec', 'thor')
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'more', 'Install merb-more from rubygems'
|
||||||
|
method_options "--merb-root" => :optional
|
||||||
|
def more
|
||||||
|
refresh_from_gems 'merb-more'
|
||||||
|
ensure_local_bin_for('merb-gen')
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'plugins', 'Install merb-plugins from rubygems'
|
||||||
|
method_options "--merb-root" => :optional
|
||||||
|
def plugins
|
||||||
|
refresh_from_gems 'merb-plugins'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'dm_core', 'Install dm-core from rubygems'
|
||||||
|
method_options "--merb-root" => :optional
|
||||||
|
def dm_core
|
||||||
|
refresh_from_gems 'extlib', 'dm-core'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'dm_more', 'Install dm-more from rubygems'
|
||||||
|
method_options "--merb-root" => :optional
|
||||||
|
def dm_more
|
||||||
|
refresh_from_gems 'extlib', 'dm-core', 'dm-more'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pull from RubyForge and install.
|
||||||
|
def refresh_from_gems(*components)
|
||||||
|
gems = Gems.new
|
||||||
|
gems.options = options
|
||||||
|
components.all? { |name| gems.install(name) }
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Retrieve latest Merb versions from git and optionally install them.
|
||||||
|
#
|
||||||
|
# Note: the --sources option takes a path to a YAML file
|
||||||
|
# with a regular Hash mapping gem names to git urls.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# thor merb:edge
|
||||||
|
# thor merb:edge --install
|
||||||
|
# thor merb:edge --merb-root ./path/to/your/app
|
||||||
|
# thor merb:edge --sources ./path/to/sources.yml
|
||||||
|
|
||||||
|
desc 'edge', 'Install extlib, merb-core and merb-more from git HEAD'
|
||||||
|
method_options "--merb-root" => :optional,
|
||||||
|
"--sources" => :optional,
|
||||||
|
"--install" => :boolean
|
||||||
|
def edge
|
||||||
|
edge = Edge.new
|
||||||
|
edge.options = options
|
||||||
|
edge.core
|
||||||
|
edge.more
|
||||||
|
end
|
||||||
|
|
||||||
|
class Edge < Thor
|
||||||
|
|
||||||
|
# The Edge tasks deal with known gems from the bleeding edge; available
|
||||||
|
# as shortcuts to Merb and DataMapper gems.
|
||||||
|
#
|
||||||
|
# These are pulled from git and optionally installed into into the
|
||||||
|
# desired gems dir (either system-wide or into the application's
|
||||||
|
# gems directory).
|
||||||
|
|
||||||
|
include MerbThorHelper
|
||||||
|
|
||||||
|
# Gets latest gem versions from git - optionally installs them.
|
||||||
|
#
|
||||||
|
# Note: the --sources option takes a path to a YAML file
|
||||||
|
# with a regular Hash mapping gem names to git urls,
|
||||||
|
# allowing pulling forks of the official repositories.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# thor merb:edge:core
|
||||||
|
# thor merb:edge:core --install
|
||||||
|
# thor merb:edge:core --merb-root ./path/to/your/app
|
||||||
|
# thor merb:edge:core --sources ./path/to/sources.yml
|
||||||
|
|
||||||
|
desc 'core', 'Update extlib and merb-core from git HEAD'
|
||||||
|
method_options "--merb-root" => :optional,
|
||||||
|
"--sources" => :optional,
|
||||||
|
"--install" => :boolean
|
||||||
|
def core
|
||||||
|
refresh_from_source 'thor', 'extlib', 'merb-core'
|
||||||
|
ensure_local_bin_for('merb-core', 'rake', 'rspec', 'thor')
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'more', 'Update merb-more from git HEAD'
|
||||||
|
method_options "--merb-root" => :optional,
|
||||||
|
"--sources" => :optional,
|
||||||
|
"--install" => :boolean
|
||||||
|
def more
|
||||||
|
refresh_from_source 'merb-more'
|
||||||
|
ensure_local_bin_for('merb-gen')
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'plugins', 'Update merb-plugins from git HEAD'
|
||||||
|
method_options "--merb-root" => :optional,
|
||||||
|
"--sources" => :optional,
|
||||||
|
"--install" => :boolean
|
||||||
|
def plugins
|
||||||
|
refresh_from_source 'merb-plugins'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'dm_core', 'Update dm-core from git HEAD'
|
||||||
|
method_options "--merb-root" => :optional,
|
||||||
|
"--sources" => :optional,
|
||||||
|
"--install" => :boolean
|
||||||
|
def dm_core
|
||||||
|
refresh_from_source 'extlib', 'dm-core'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'dm_more', 'Update dm-more from git HEAD'
|
||||||
|
method_options "--merb-root" => :optional,
|
||||||
|
"--sources" => :optional,
|
||||||
|
"--install" => :boolean
|
||||||
|
def dm_more
|
||||||
|
refresh_from_source 'extlib', 'dm-core', 'dm-more'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Pull from git and optionally install the resulting gems.
|
||||||
|
def refresh_from_source(*components)
|
||||||
|
source = Source.new
|
||||||
|
source.options = options
|
||||||
|
components.each do |name|
|
||||||
|
source.clone(name)
|
||||||
|
source.install(name) if options[:install]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class Source < Thor
|
||||||
|
|
||||||
|
# The Source tasks deal with gem source packages - mainly from github.
|
||||||
|
# Any directory inside ./src is regarded as a gem that can be packaged
|
||||||
|
# and installed from there into the desired gems dir (either system-wide
|
||||||
|
# or into the application's gems directory).
|
||||||
|
|
||||||
|
include MerbThorHelper
|
||||||
|
|
||||||
|
# Install a particular gem from source.
|
||||||
|
#
|
||||||
|
# If a local ./gems dir is found, or --merb-root is given
|
||||||
|
# the gems will be installed locally into that directory.
|
||||||
|
#
|
||||||
|
# Note that this task doesn't retrieve any (new) source from git;
|
||||||
|
# To update and install you'd execute the following two tasks:
|
||||||
|
#
|
||||||
|
# thor merb:source:update merb-core
|
||||||
|
# thor merb:source:install merb-core
|
||||||
|
#
|
||||||
|
# Alternatively, look at merb:edge and merb:edge:* with --install.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# thor merb:source:install merb-core
|
||||||
|
# thor merb:source:install merb-more
|
||||||
|
# thor merb:source:install merb-more/merb-slices
|
||||||
|
# thor merb:source:install merb-plugins/merb_helpers
|
||||||
|
# thor merb:source:install merb-core --merb-root ./path/to/your/app
|
||||||
|
|
||||||
|
desc 'install GEM_NAME', 'Install a rubygem from (git) source'
|
||||||
|
method_options "--merb-root" => :optional
|
||||||
|
def install(name)
|
||||||
|
puts "Installing #{name}..."
|
||||||
|
gem_src_dir = File.join(source_dir, name)
|
||||||
|
opts = {}
|
||||||
|
opts[:install_dir] = gem_dir if gem_dir
|
||||||
|
Merb.install_gem_from_src(gem_src_dir, opts)
|
||||||
|
rescue Merb::SourcePathMissing
|
||||||
|
puts "Missing rubygem source path: #{gem_src_dir}"
|
||||||
|
rescue Merb::GemPathMissing
|
||||||
|
puts "Missing rubygems path: #{gem_dir}"
|
||||||
|
rescue => e
|
||||||
|
puts "Failed to install #{name} (#{e.message})"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Clone a git repository into ./src. The repository can be
|
||||||
|
# a direct git url or a known -named- repository.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# thor merb:source:clone dm-core
|
||||||
|
# thor merb:source:clone dm-core --sources ./path/to/sources.yml
|
||||||
|
# thor merb:source:clone git://github.com/sam/dm-core.git
|
||||||
|
|
||||||
|
desc 'clone REPOSITORY', 'Clone a git repository into ./src'
|
||||||
|
method_options "--sources" => :optional
|
||||||
|
def clone(repository)
|
||||||
|
if repository =~ /^git:\/\//
|
||||||
|
repository_url = repository
|
||||||
|
elsif url = Merb.repos(options[:sources])[repository]
|
||||||
|
repository_url = url
|
||||||
|
end
|
||||||
|
|
||||||
|
if repository_url
|
||||||
|
repository_name = repository_url[/([\w+|-]+)\.git/u, 1]
|
||||||
|
fork_name = repository_url[/.com\/+?(.+)\/.+\.git/u, 1]
|
||||||
|
local_repo_path = "#{source_dir}/#{repository_name}"
|
||||||
|
|
||||||
|
if File.directory?(local_repo_path)
|
||||||
|
puts "\n#{repository_name} repository exists, updating or branching instead of cloning..."
|
||||||
|
FileUtils.cd(local_repo_path) do
|
||||||
|
|
||||||
|
# to avoid conflicts we need to set a remote branch for non official repos
|
||||||
|
existing_repos = `git remote -v`.split("\n").map{|branch| branch.split(/\s+/)}
|
||||||
|
origin_repo_url = existing_repos.detect{ |r| r.first == "origin" }.last
|
||||||
|
|
||||||
|
# pull from the original repository - no branching needed
|
||||||
|
if repository_url == origin_repo_url
|
||||||
|
puts "Pulling from #{repository_url}"
|
||||||
|
system %{
|
||||||
|
git fetch
|
||||||
|
git checkout master
|
||||||
|
git rebase origin/master
|
||||||
|
}
|
||||||
|
# update and switch to a branch for a particular github fork
|
||||||
|
elsif existing_repos.map{ |r| r.last }.include?(repository_url)
|
||||||
|
puts "Switching to remote branch: #{fork_name}"
|
||||||
|
`git checkout -b #{fork_name} #{fork_name}/master`
|
||||||
|
`git rebase #{fork_name}/master`
|
||||||
|
# create a new remote branch for a particular github fork
|
||||||
|
else
|
||||||
|
puts "Add a new remote branch: #{fork_name}"
|
||||||
|
`git remote add -f #{fork_name} #{repository_url}`
|
||||||
|
`git checkout -b#{fork_name} #{fork_name}/master`
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
FileUtils.cd(source_dir) do
|
||||||
|
puts "\nCloning #{repository_name} repository from #{repository_url}..."
|
||||||
|
system("git clone --depth=1 #{repository_url} ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
puts "No valid repository url given"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update a specific gem source directory from git. See #clone.
|
||||||
|
|
||||||
|
desc 'update REPOSITORY_URL', 'Update a git repository in ./src'
|
||||||
|
alias :update :clone
|
||||||
|
|
||||||
|
# Update all gem sources from git - based on the current branch.
|
||||||
|
|
||||||
|
desc 'refresh', 'Pull fresh copies of all source gems'
|
||||||
|
def refresh
|
||||||
|
repos = Dir["#{source_dir}/*"]
|
||||||
|
repos.each do |repo|
|
||||||
|
next unless File.directory?(repo) && File.exists?(File.join(repo, '.git'))
|
||||||
|
FileUtils.cd(repo) do
|
||||||
|
puts "Refreshing #{File.basename(repo)}"
|
||||||
|
branch = `git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1) /'`[/\* (.+)/, 1]
|
||||||
|
system %{git rebase #{branch}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class Gems < Thor
|
||||||
|
|
||||||
|
# The Gems tasks deal directly with rubygems, either through remotely
|
||||||
|
# available sources (rubyforge for example) or by searching the
|
||||||
|
# system-wide gem cache for matching gems. The gems are installed from
|
||||||
|
# there into the desired gems dir (either system-wide or into the
|
||||||
|
# application's gems directory).
|
||||||
|
|
||||||
|
include MerbThorHelper
|
||||||
|
|
||||||
|
# Install a gem and its dependencies.
|
||||||
|
#
|
||||||
|
# If a local ./gems dir is found, or --merb-root is given
|
||||||
|
# the gems will be installed locally into that directory.
|
||||||
|
#
|
||||||
|
# The option --cache will look in the system's gem cache
|
||||||
|
# for the latest version and install it in the apps' gems.
|
||||||
|
# This is particularly handy for gems that aren't available
|
||||||
|
# through rubyforge.org - like in-house merb slices etc.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# thor merb:gems:install merb-core
|
||||||
|
# thor merb:gems:install merb-core --cache
|
||||||
|
# thor merb:gems:install merb-core --version 0.9.7
|
||||||
|
# thor merb:gems:install merb-core --merb-root ./path/to/your/app
|
||||||
|
|
||||||
|
desc 'install GEM_NAME', 'Install a gem from rubygems'
|
||||||
|
method_options "--version" => :optional,
|
||||||
|
"--merb-root" => :optional,
|
||||||
|
"--cache" => :boolean
|
||||||
|
def install(name)
|
||||||
|
puts "Installing #{name}..."
|
||||||
|
opts = {}
|
||||||
|
opts[:version] = options[:version]
|
||||||
|
opts[:cache] = options[:cache] if gem_dir
|
||||||
|
opts[:install_dir] = gem_dir if gem_dir
|
||||||
|
Merb.install_gem(name, opts)
|
||||||
|
rescue => e
|
||||||
|
puts "Failed to install #{name} (#{e.message})"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update a gem and its dependencies.
|
||||||
|
#
|
||||||
|
# If a local ./gems dir is found, or --merb-root is given
|
||||||
|
# the gems will be installed locally into that directory.
|
||||||
|
#
|
||||||
|
# The option --cache will look in the system's gem cache
|
||||||
|
# for the latest version and install it in the apps' gems.
|
||||||
|
# This is particularly handy for gems that aren't available
|
||||||
|
# through rubyforge.org - like in-house merb slices etc.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# thor merb:gems:update merb-core
|
||||||
|
# thor merb:gems:update merb-core --cache
|
||||||
|
# thor merb:gems:update merb-core --merb-root ./path/to/your/app
|
||||||
|
|
||||||
|
desc 'update GEM_NAME', 'Update a gem from rubygems'
|
||||||
|
method_options "--merb-root" => :optional,
|
||||||
|
"--cache" => :boolean
|
||||||
|
def update(name)
|
||||||
|
puts "Updating #{name}..."
|
||||||
|
opts = {}
|
||||||
|
if gem_dir
|
||||||
|
if gemspec_path = Dir[File.join(gem_dir, 'specifications', "#{name}-*.gemspec")].last
|
||||||
|
gemspec = Gem::Specification.load(gemspec_path)
|
||||||
|
opts[:version] = Gem::Requirement.new [">#{gemspec.version}"]
|
||||||
|
end
|
||||||
|
opts[:install_dir] = gem_dir
|
||||||
|
opts[:cache] = options[:cache]
|
||||||
|
end
|
||||||
|
Merb.install_gem(name, opts)
|
||||||
|
rescue => e
|
||||||
|
puts "Failed to update #{name} (#{e.message})"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Uninstall a gem - ignores dependencies.
|
||||||
|
#
|
||||||
|
# If a local ./gems dir is found, or --merb-root is given
|
||||||
|
# the gems will be uninstalled locally from that directory.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# thor merb:gems:uninstall merb-core
|
||||||
|
# thor merb:gems:uninstall merb-core --all
|
||||||
|
# thor merb:gems:uninstall merb-core --version 0.9.7
|
||||||
|
# thor merb:gems:uninstall merb-core --merb-root ./path/to/your/app
|
||||||
|
|
||||||
|
desc 'install GEM_NAME', 'Install a gem from rubygems'
|
||||||
|
desc 'uninstall GEM_NAME', 'Uninstall a gem'
|
||||||
|
method_options "--version" => :optional,
|
||||||
|
"--merb-root" => :optional,
|
||||||
|
"--all" => :boolean
|
||||||
|
def uninstall(name)
|
||||||
|
puts "Uninstalling #{name}..."
|
||||||
|
opts = {}
|
||||||
|
opts[:ignore] = true
|
||||||
|
opts[:all] = options[:all]
|
||||||
|
opts[:executables] = true
|
||||||
|
opts[:version] = options[:version]
|
||||||
|
opts[:install_dir] = gem_dir if gem_dir
|
||||||
|
Merb.uninstall_gem(name, opts)
|
||||||
|
rescue => e
|
||||||
|
puts "Failed to uninstall #{name} (#{e.message})"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Completely remove a gem and all its versions - ignores dependencies.
|
||||||
|
#
|
||||||
|
# If a local ./gems dir is found, or --merb-root is given
|
||||||
|
# the gems will be uninstalled locally from that directory.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
#
|
||||||
|
# thor merb:gems:wipe merb-core
|
||||||
|
# thor merb:gems:wipe merb-core --merb-root ./path/to/your/app
|
||||||
|
|
||||||
|
desc 'wipe GEM_NAME', 'Remove a gem completely'
|
||||||
|
method_options "--merb-root" => :optional
|
||||||
|
def wipe(name)
|
||||||
|
puts "Wiping #{name}..."
|
||||||
|
opts = {}
|
||||||
|
opts[:ignore] = true
|
||||||
|
opts[:all] = true
|
||||||
|
opts[:executables] = true
|
||||||
|
opts[:install_dir] = gem_dir if gem_dir
|
||||||
|
Merb.uninstall_gem(name, opts)
|
||||||
|
rescue => e
|
||||||
|
puts "Failed to wipe #{name} (#{e.message})"
|
||||||
|
end
|
||||||
|
|
||||||
|
# This task should be executed as part of a deployment setup, where
|
||||||
|
# the deployment system runs this after the app has been installed.
|
||||||
|
# Usually triggered by Capistrano, God...
|
||||||
|
#
|
||||||
|
# It will regenerate gems from the bundled gems cache for any gem
|
||||||
|
# that has C extensions - which need to be recompiled for the target
|
||||||
|
# deployment platform.
|
||||||
|
|
||||||
|
desc 'redeploy', 'Recreate any binary gems on the target deployment platform'
|
||||||
|
def redeploy
|
||||||
|
require 'tempfile' # for
|
||||||
|
if File.directory?(specs_dir = File.join(gem_dir, 'specifications')) &&
|
||||||
|
File.directory?(cache_dir = File.join(gem_dir, 'cache'))
|
||||||
|
Dir[File.join(specs_dir, '*.gemspec')].each do |gemspec_path|
|
||||||
|
unless (gemspec = Gem::Specification.load(gemspec_path)).extensions.empty?
|
||||||
|
if File.exists?(gem_file = File.join(cache_dir, "#{gemspec.full_name}.gem"))
|
||||||
|
gem_file_copy = File.join(Dir::tmpdir, File.basename(gem_file))
|
||||||
|
# Copy the gem to a temporary file, because otherwise RubyGems/FileUtils
|
||||||
|
# will complain about copying identical files (same source/destination).
|
||||||
|
FileUtils.cp(gem_file, gem_file_copy)
|
||||||
|
Merb.install_gem(gem_file_copy, :install_dir => gem_dir)
|
||||||
|
File.delete(gem_file_copy)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
puts "No application local gems directory found"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class << self
|
||||||
|
|
||||||
|
# Default Git repositories - pass source_config option
|
||||||
|
# to load a yaml configuration file.
|
||||||
|
def repos(source_config = nil)
|
||||||
|
@_repos ||= begin
|
||||||
|
repositories = {
|
||||||
|
'merb-core' => "git://github.com/wycats/merb-core.git",
|
||||||
|
'merb-more' => "git://github.com/wycats/merb-more.git",
|
||||||
|
'merb-plugins' => "git://github.com/wycats/merb-plugins.git",
|
||||||
|
'extlib' => "git://github.com/sam/extlib.git",
|
||||||
|
'dm-core' => "git://github.com/sam/dm-core.git",
|
||||||
|
'dm-more' => "git://github.com/sam/dm-more.git",
|
||||||
|
'thor' => "git://github.com/wycats/thor.git"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
if source_config && File.exists?(source_config)
|
||||||
|
@_repos.merge(YAML.load(File.read(source_config)))
|
||||||
|
else
|
||||||
|
@_repos
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Install a gem - looks remotely and local gem cache;
|
||||||
|
# won't process rdoc or ri options.
|
||||||
|
def install_gem(gem, options = {})
|
||||||
|
from_cache = (options.key?(:cache) && options.delete(:cache))
|
||||||
|
if from_cache
|
||||||
|
install_gem_from_cache(gem, options)
|
||||||
|
else
|
||||||
|
version = options.delete(:version)
|
||||||
|
Gem.configuration.update_sources = false
|
||||||
|
installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
|
||||||
|
exception = nil
|
||||||
|
begin
|
||||||
|
installer.install gem, version
|
||||||
|
rescue Gem::InstallError => e
|
||||||
|
exception = e
|
||||||
|
rescue Gem::GemNotFoundException => e
|
||||||
|
if from_cache && gem_file = find_gem_in_cache(gem, version)
|
||||||
|
puts "Located #{gem} in gem cache..."
|
||||||
|
installer.install gem_file
|
||||||
|
else
|
||||||
|
exception = e
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
exception = e
|
||||||
|
end
|
||||||
|
if installer.installed_gems.empty? && exception
|
||||||
|
puts "Failed to install gem '#{gem}' (#{exception.message})"
|
||||||
|
end
|
||||||
|
installer.installed_gems.each do |spec|
|
||||||
|
puts "Successfully installed #{spec.full_name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Install a gem - looks in the system's gem cache instead of remotely;
|
||||||
|
# won't process rdoc or ri options.
|
||||||
|
def install_gem_from_cache(gem, options = {})
|
||||||
|
version = options.delete(:version)
|
||||||
|
Gem.configuration.update_sources = false
|
||||||
|
installer = Gem::DependencyInstaller.new(options.merge(:user_install => false))
|
||||||
|
exception = nil
|
||||||
|
begin
|
||||||
|
if gem_file = find_gem_in_cache(gem, version)
|
||||||
|
puts "Located #{gem} in gem cache..."
|
||||||
|
installer.install gem_file
|
||||||
|
else
|
||||||
|
raise Gem::InstallError, "Unknown gem #{gem}"
|
||||||
|
end
|
||||||
|
rescue Gem::InstallError => e
|
||||||
|
exception = e
|
||||||
|
end
|
||||||
|
if installer.installed_gems.empty? && exception
|
||||||
|
puts "Failed to install gem '#{gem}' (#{e.message})"
|
||||||
|
end
|
||||||
|
installer.installed_gems.each do |spec|
|
||||||
|
puts "Successfully installed #{spec.full_name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Install a gem from source - builds and packages it first then installs it.
|
||||||
|
def install_gem_from_src(gem_src_dir, options = {})
|
||||||
|
raise SourcePathMissing unless File.directory?(gem_src_dir)
|
||||||
|
raise GemPathMissing if options[:install_dir] && !File.directory?(options[:install_dir])
|
||||||
|
|
||||||
|
gem_name = File.basename(gem_src_dir)
|
||||||
|
gem_pkg_dir = File.expand_path(File.join(gem_src_dir, 'pkg'))
|
||||||
|
|
||||||
|
# We need to use local bin executables if available.
|
||||||
|
thor = which('thor')
|
||||||
|
rake = which('rake')
|
||||||
|
|
||||||
|
# Handle pure Thor installation instead of Rake
|
||||||
|
if File.exists?(File.join(gem_src_dir, 'Thorfile'))
|
||||||
|
# Remove any existing packages.
|
||||||
|
FileUtils.rm_rf(gem_pkg_dir) if File.directory?(gem_pkg_dir)
|
||||||
|
# Create the package.
|
||||||
|
FileUtils.cd(gem_src_dir) { system("#{thor} :package") }
|
||||||
|
# Install the package using rubygems.
|
||||||
|
if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
|
||||||
|
FileUtils.cd(File.dirname(package)) do
|
||||||
|
install_gem(File.basename(package), options.dup)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise Merb::GemInstallError, "No package found for #{gem_name}"
|
||||||
|
end
|
||||||
|
# Handle standard installation through Rake
|
||||||
|
else
|
||||||
|
# Clean and regenerate any subgems for meta gems.
|
||||||
|
Dir[File.join(gem_src_dir, '*', 'Rakefile')].each do |rakefile|
|
||||||
|
FileUtils.cd(File.dirname(rakefile)) { system("#{rake} clobber_package; #{rake} package") }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Handle the main gem install.
|
||||||
|
if File.exists?(File.join(gem_src_dir, 'Rakefile'))
|
||||||
|
# Remove any existing packages.
|
||||||
|
FileUtils.cd(gem_src_dir) { system("#{rake} clobber_package") }
|
||||||
|
# Create the main gem pkg dir if it doesn't exist.
|
||||||
|
FileUtils.mkdir_p(gem_pkg_dir) unless File.directory?(gem_pkg_dir)
|
||||||
|
# Copy any subgems to the main gem pkg dir.
|
||||||
|
Dir[File.join(gem_src_dir, '**', 'pkg', '*.gem')].each do |subgem_pkg|
|
||||||
|
FileUtils.cp(subgem_pkg, gem_pkg_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Finally generate the main package and install it; subgems
|
||||||
|
# (dependencies) are local to the main package.
|
||||||
|
FileUtils.cd(gem_src_dir) do
|
||||||
|
system("#{rake} package")
|
||||||
|
if package = Dir[File.join(gem_pkg_dir, "#{gem_name}-*.gem")].last
|
||||||
|
FileUtils.cd(File.dirname(package)) do
|
||||||
|
install_gem(File.basename(package), options.dup)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise Merb::GemInstallError, "No package found for #{gem_name}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
raise Merb::GemInstallError, "No Rakefile found for #{gem_name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Uninstall a gem.
|
||||||
|
def uninstall_gem(gem, options = {})
|
||||||
|
if options[:version] && !options[:version].is_a?(Gem::Requirement)
|
||||||
|
options[:version] = Gem::Requirement.new ["= #{version}"]
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
Gem::Uninstaller.new(gem, options).uninstall
|
||||||
|
rescue => e
|
||||||
|
raise GemUninstallError, "Failed to uninstall #{gem}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Will prepend sudo on a suitable platform.
|
||||||
|
def sudo
|
||||||
|
@_sudo ||= begin
|
||||||
|
windows = PLATFORM =~ /win32|cygwin/ rescue nil
|
||||||
|
windows ? "" : "sudo "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Use the local bin/* executables if available.
|
||||||
|
def which(executable)
|
||||||
|
if File.executable?(exec = File.join(Dir.pwd, 'bin', executable))
|
||||||
|
exec
|
||||||
|
else
|
||||||
|
executable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def find_gem_in_cache(gem, version)
|
||||||
|
spec = if version
|
||||||
|
version = Gem::Requirement.new ["= #{version}"] unless version.is_a?(Gem::Requirement)
|
||||||
|
Gem.source_index.find_name(gem, version).first
|
||||||
|
else
|
||||||
|
Gem.source_index.find_name(gem).sort_by { |g| g.version }.last
|
||||||
|
end
|
||||||
|
if spec && File.exists?(gem_file = "#{spec.installation_path}/cache/#{spec.full_name}.gem")
|
||||||
|
gem_file
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class Tasks < Thor
|
||||||
|
|
||||||
|
include MerbThorHelper
|
||||||
|
|
||||||
|
# Install Thor, Rake and RSpec into the local gems dir, by copying it from
|
||||||
|
# the system-wide rubygems cache - which is OK since we needed it to run
|
||||||
|
# this task already.
|
||||||
|
#
|
||||||
|
# After this we don't need the system-wide rubygems anymore, as all required
|
||||||
|
# executables are available in the local ./bin directory.
|
||||||
|
#
|
||||||
|
# RSpec is needed here because source installs might fail when running
|
||||||
|
# rake tasks where spec/rake/spectask has been required.
|
||||||
|
|
||||||
|
desc 'setup', 'Install Thor, Rake and RSpec in the local gems dir'
|
||||||
|
method_options "--merb-root" => :optional
|
||||||
|
def setup
|
||||||
|
if $0 =~ /^(\.\/)?bin\/thor$/
|
||||||
|
puts "You cannot run the setup from #{$0} - try #{File.basename($0)} merb:tasks:setup instead"
|
||||||
|
return
|
||||||
|
end
|
||||||
|
create_if_missing(File.join(working_dir, 'gems'))
|
||||||
|
Merb.install_gem('thor', :cache => true, :install_dir => gem_dir)
|
||||||
|
Merb.install_gem('rake', :cache => true, :install_dir => gem_dir)
|
||||||
|
Merb.install_gem('rspec', :cache => true, :install_dir => gem_dir)
|
||||||
|
ensure_local_bin_for('thor', 'rake', 'rspec')
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 626 B |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 1005 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 990 B |
After Width: | Height: | Size: 1002 B |
|
@ -0,0 +1,28 @@
|
||||||
|
function addLoadEvent(func)
|
||||||
|
{
|
||||||
|
var oldonload = window.onload;
|
||||||
|
if (typeof window.onload != 'function')
|
||||||
|
{
|
||||||
|
window.onload = func;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.onload = function()
|
||||||
|
{
|
||||||
|
oldonload();
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide_flashes()
|
||||||
|
{
|
||||||
|
addLoadEvent(function(){
|
||||||
|
setTimeout('hide_flashes_timer()', 30000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide_flashes_timer()
|
||||||
|
{
|
||||||
|
new Effect.DropOut($('flash_container'));
|
||||||
|
}
|
|
@ -0,0 +1,974 @@
|
||||||
|
// script.aculo.us dragdrop.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
|
||||||
|
|
||||||
|
// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||||
|
// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||||
|
//
|
||||||
|
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||||
|
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||||
|
|
||||||
|
if(Object.isUndefined(Effect))
|
||||||
|
throw("dragdrop.js requires including script.aculo.us' effects.js library");
|
||||||
|
|
||||||
|
var Droppables = {
|
||||||
|
drops: [],
|
||||||
|
|
||||||
|
remove: function(element) {
|
||||||
|
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
|
||||||
|
},
|
||||||
|
|
||||||
|
add: function(element) {
|
||||||
|
element = $(element);
|
||||||
|
var options = Object.extend({
|
||||||
|
greedy: true,
|
||||||
|
hoverclass: null,
|
||||||
|
tree: false
|
||||||
|
}, arguments[1] || { });
|
||||||
|
|
||||||
|
// cache containers
|
||||||
|
if(options.containment) {
|
||||||
|
options._containers = [];
|
||||||
|
var containment = options.containment;
|
||||||
|
if(Object.isArray(containment)) {
|
||||||
|
containment.each( function(c) { options._containers.push($(c)) });
|
||||||
|
} else {
|
||||||
|
options._containers.push($(containment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(options.accept) options.accept = [options.accept].flatten();
|
||||||
|
|
||||||
|
Element.makePositioned(element); // fix IE
|
||||||
|
options.element = element;
|
||||||
|
|
||||||
|
this.drops.push(options);
|
||||||
|
},
|
||||||
|
|
||||||
|
findDeepestChild: function(drops) {
|
||||||
|
deepest = drops[0];
|
||||||
|
|
||||||
|
for (i = 1; i < drops.length; ++i)
|
||||||
|
if (Element.isParent(drops[i].element, deepest.element))
|
||||||
|
deepest = drops[i];
|
||||||
|
|
||||||
|
return deepest;
|
||||||
|
},
|
||||||
|
|
||||||
|
isContained: function(element, drop) {
|
||||||
|
var containmentNode;
|
||||||
|
if(drop.tree) {
|
||||||
|
containmentNode = element.treeNode;
|
||||||
|
} else {
|
||||||
|
containmentNode = element.parentNode;
|
||||||
|
}
|
||||||
|
return drop._containers.detect(function(c) { return containmentNode == c });
|
||||||
|
},
|
||||||
|
|
||||||
|
isAffected: function(point, element, drop) {
|
||||||
|
return (
|
||||||
|
(drop.element!=element) &&
|
||||||
|
((!drop._containers) ||
|
||||||
|
this.isContained(element, drop)) &&
|
||||||
|
((!drop.accept) ||
|
||||||
|
(Element.classNames(element).detect(
|
||||||
|
function(v) { return drop.accept.include(v) } ) )) &&
|
||||||
|
Position.within(drop.element, point[0], point[1]) );
|
||||||
|
},
|
||||||
|
|
||||||
|
deactivate: function(drop) {
|
||||||
|
if(drop.hoverclass)
|
||||||
|
Element.removeClassName(drop.element, drop.hoverclass);
|
||||||
|
this.last_active = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
activate: function(drop) {
|
||||||
|
if(drop.hoverclass)
|
||||||
|
Element.addClassName(drop.element, drop.hoverclass);
|
||||||
|
this.last_active = drop;
|
||||||
|
},
|
||||||
|
|
||||||
|
show: function(point, element) {
|
||||||
|
if(!this.drops.length) return;
|
||||||
|
var drop, affected = [];
|
||||||
|
|
||||||
|
this.drops.each( function(drop) {
|
||||||
|
if(Droppables.isAffected(point, element, drop))
|
||||||
|
affected.push(drop);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(affected.length>0)
|
||||||
|
drop = Droppables.findDeepestChild(affected);
|
||||||
|
|
||||||
|
if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
|
||||||
|
if (drop) {
|
||||||
|
Position.within(drop.element, point[0], point[1]);
|
||||||
|
if(drop.onHover)
|
||||||
|
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
|
||||||
|
|
||||||
|
if (drop != this.last_active) Droppables.activate(drop);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fire: function(event, element) {
|
||||||
|
if(!this.last_active) return;
|
||||||
|
Position.prepare();
|
||||||
|
|
||||||
|
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
|
||||||
|
if (this.last_active.onDrop) {
|
||||||
|
this.last_active.onDrop(element, this.last_active.element, event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function() {
|
||||||
|
if(this.last_active)
|
||||||
|
this.deactivate(this.last_active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var Draggables = {
|
||||||
|
drags: [],
|
||||||
|
observers: [],
|
||||||
|
|
||||||
|
register: function(draggable) {
|
||||||
|
if(this.drags.length == 0) {
|
||||||
|
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
||||||
|
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
|
||||||
|
this.eventKeypress = this.keyPress.bindAsEventListener(this);
|
||||||
|
|
||||||
|
Event.observe(document, "mouseup", this.eventMouseUp);
|
||||||
|
Event.observe(document, "mousemove", this.eventMouseMove);
|
||||||
|
Event.observe(document, "keypress", this.eventKeypress);
|
||||||
|
}
|
||||||
|
this.drags.push(draggable);
|
||||||
|
},
|
||||||
|
|
||||||
|
unregister: function(draggable) {
|
||||||
|
this.drags = this.drags.reject(function(d) { return d==draggable });
|
||||||
|
if(this.drags.length == 0) {
|
||||||
|
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
||||||
|
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
||||||
|
Event.stopObserving(document, "keypress", this.eventKeypress);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
activate: function(draggable) {
|
||||||
|
if(draggable.options.delay) {
|
||||||
|
this._timeout = setTimeout(function() {
|
||||||
|
Draggables._timeout = null;
|
||||||
|
window.focus();
|
||||||
|
Draggables.activeDraggable = draggable;
|
||||||
|
}.bind(this), draggable.options.delay);
|
||||||
|
} else {
|
||||||
|
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
|
||||||
|
this.activeDraggable = draggable;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deactivate: function() {
|
||||||
|
this.activeDraggable = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDrag: function(event) {
|
||||||
|
if(!this.activeDraggable) return;
|
||||||
|
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||||
|
// Mozilla-based browsers fire successive mousemove events with
|
||||||
|
// the same coordinates, prevent needless redrawing (moz bug?)
|
||||||
|
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
|
||||||
|
this._lastPointer = pointer;
|
||||||
|
|
||||||
|
this.activeDraggable.updateDrag(event, pointer);
|
||||||
|
},
|
||||||
|
|
||||||
|
endDrag: function(event) {
|
||||||
|
if(this._timeout) {
|
||||||
|
clearTimeout(this._timeout);
|
||||||
|
this._timeout = null;
|
||||||
|
}
|
||||||
|
if(!this.activeDraggable) return;
|
||||||
|
this._lastPointer = null;
|
||||||
|
this.activeDraggable.endDrag(event);
|
||||||
|
this.activeDraggable = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
keyPress: function(event) {
|
||||||
|
if(this.activeDraggable)
|
||||||
|
this.activeDraggable.keyPress(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
addObserver: function(observer) {
|
||||||
|
this.observers.push(observer);
|
||||||
|
this._cacheObserverCallbacks();
|
||||||
|
},
|
||||||
|
|
||||||
|
removeObserver: function(element) { // element instead of observer fixes mem leaks
|
||||||
|
this.observers = this.observers.reject( function(o) { return o.element==element });
|
||||||
|
this._cacheObserverCallbacks();
|
||||||
|
},
|
||||||
|
|
||||||
|
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
|
||||||
|
if(this[eventName+'Count'] > 0)
|
||||||
|
this.observers.each( function(o) {
|
||||||
|
if(o[eventName]) o[eventName](eventName, draggable, event);
|
||||||
|
});
|
||||||
|
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
|
||||||
|
},
|
||||||
|
|
||||||
|
_cacheObserverCallbacks: function() {
|
||||||
|
['onStart','onEnd','onDrag'].each( function(eventName) {
|
||||||
|
Draggables[eventName+'Count'] = Draggables.observers.select(
|
||||||
|
function(o) { return o[eventName]; }
|
||||||
|
).length;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
var Draggable = Class.create({
|
||||||
|
initialize: function(element) {
|
||||||
|
var defaults = {
|
||||||
|
handle: false,
|
||||||
|
reverteffect: function(element, top_offset, left_offset) {
|
||||||
|
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
||||||
|
new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
|
||||||
|
queue: {scope:'_draggable', position:'end'}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
endeffect: function(element) {
|
||||||
|
var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
|
||||||
|
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
|
||||||
|
queue: {scope:'_draggable', position:'end'},
|
||||||
|
afterFinish: function(){
|
||||||
|
Draggable._dragging[element] = false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
zindex: 1000,
|
||||||
|
revert: false,
|
||||||
|
quiet: false,
|
||||||
|
scroll: false,
|
||||||
|
scrollSensitivity: 20,
|
||||||
|
scrollSpeed: 15,
|
||||||
|
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||||
|
delay: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
|
||||||
|
Object.extend(defaults, {
|
||||||
|
starteffect: function(element) {
|
||||||
|
element._opacity = Element.getOpacity(element);
|
||||||
|
Draggable._dragging[element] = true;
|
||||||
|
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var options = Object.extend(defaults, arguments[1] || { });
|
||||||
|
|
||||||
|
this.element = $(element);
|
||||||
|
|
||||||
|
if(options.handle && Object.isString(options.handle))
|
||||||
|
this.handle = this.element.down('.'+options.handle, 0);
|
||||||
|
|
||||||
|
if(!this.handle) this.handle = $(options.handle);
|
||||||
|
if(!this.handle) this.handle = this.element;
|
||||||
|
|
||||||
|
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
|
||||||
|
options.scroll = $(options.scroll);
|
||||||
|
this._isScrollChild = Element.childOf(this.element, options.scroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
Element.makePositioned(this.element); // fix IE
|
||||||
|
|
||||||
|
this.options = options;
|
||||||
|
this.dragging = false;
|
||||||
|
|
||||||
|
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
|
||||||
|
Event.observe(this.handle, "mousedown", this.eventMouseDown);
|
||||||
|
|
||||||
|
Draggables.register(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
|
||||||
|
Draggables.unregister(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
currentDelta: function() {
|
||||||
|
return([
|
||||||
|
parseInt(Element.getStyle(this.element,'left') || '0'),
|
||||||
|
parseInt(Element.getStyle(this.element,'top') || '0')]);
|
||||||
|
},
|
||||||
|
|
||||||
|
initDrag: function(event) {
|
||||||
|
if(!Object.isUndefined(Draggable._dragging[this.element]) &&
|
||||||
|
Draggable._dragging[this.element]) return;
|
||||||
|
if(Event.isLeftClick(event)) {
|
||||||
|
// abort on form elements, fixes a Firefox issue
|
||||||
|
var src = Event.element(event);
|
||||||
|
if((tag_name = src.tagName.toUpperCase()) && (
|
||||||
|
tag_name=='INPUT' ||
|
||||||
|
tag_name=='SELECT' ||
|
||||||
|
tag_name=='OPTION' ||
|
||||||
|
tag_name=='BUTTON' ||
|
||||||
|
tag_name=='TEXTAREA')) return;
|
||||||
|
|
||||||
|
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||||
|
var pos = Position.cumulativeOffset(this.element);
|
||||||
|
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
|
||||||
|
|
||||||
|
Draggables.activate(this);
|
||||||
|
Event.stop(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startDrag: function(event) {
|
||||||
|
this.dragging = true;
|
||||||
|
if(!this.delta)
|
||||||
|
this.delta = this.currentDelta();
|
||||||
|
|
||||||
|
if(this.options.zindex) {
|
||||||
|
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
|
||||||
|
this.element.style.zIndex = this.options.zindex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.options.ghosting) {
|
||||||
|
this._clone = this.element.cloneNode(true);
|
||||||
|
this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
|
||||||
|
if (!this.element._originallyAbsolute)
|
||||||
|
Position.absolutize(this.element);
|
||||||
|
this.element.parentNode.insertBefore(this._clone, this.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.options.scroll) {
|
||||||
|
if (this.options.scroll == window) {
|
||||||
|
var where = this._getWindowScroll(this.options.scroll);
|
||||||
|
this.originalScrollLeft = where.left;
|
||||||
|
this.originalScrollTop = where.top;
|
||||||
|
} else {
|
||||||
|
this.originalScrollLeft = this.options.scroll.scrollLeft;
|
||||||
|
this.originalScrollTop = this.options.scroll.scrollTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Draggables.notify('onStart', this, event);
|
||||||
|
|
||||||
|
if(this.options.starteffect) this.options.starteffect(this.element);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDrag: function(event, pointer) {
|
||||||
|
if(!this.dragging) this.startDrag(event);
|
||||||
|
|
||||||
|
if(!this.options.quiet){
|
||||||
|
Position.prepare();
|
||||||
|
Droppables.show(pointer, this.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
Draggables.notify('onDrag', this, event);
|
||||||
|
|
||||||
|
this.draw(pointer);
|
||||||
|
if(this.options.change) this.options.change(this);
|
||||||
|
|
||||||
|
if(this.options.scroll) {
|
||||||
|
this.stopScrolling();
|
||||||
|
|
||||||
|
var p;
|
||||||
|
if (this.options.scroll == window) {
|
||||||
|
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
|
||||||
|
} else {
|
||||||
|
p = Position.page(this.options.scroll);
|
||||||
|
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
|
||||||
|
p[1] += this.options.scroll.scrollTop + Position.deltaY;
|
||||||
|
p.push(p[0]+this.options.scroll.offsetWidth);
|
||||||
|
p.push(p[1]+this.options.scroll.offsetHeight);
|
||||||
|
}
|
||||||
|
var speed = [0,0];
|
||||||
|
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
|
||||||
|
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
|
||||||
|
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
|
||||||
|
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
|
||||||
|
this.startScrolling(speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix AppleWebKit rendering
|
||||||
|
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
|
||||||
|
|
||||||
|
Event.stop(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
finishDrag: function(event, success) {
|
||||||
|
this.dragging = false;
|
||||||
|
|
||||||
|
if(this.options.quiet){
|
||||||
|
Position.prepare();
|
||||||
|
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||||
|
Droppables.show(pointer, this.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.options.ghosting) {
|
||||||
|
if (!this.element._originallyAbsolute)
|
||||||
|
Position.relativize(this.element);
|
||||||
|
delete this.element._originallyAbsolute;
|
||||||
|
Element.remove(this._clone);
|
||||||
|
this._clone = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dropped = false;
|
||||||
|
if(success) {
|
||||||
|
dropped = Droppables.fire(event, this.element);
|
||||||
|
if (!dropped) dropped = false;
|
||||||
|
}
|
||||||
|
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
|
||||||
|
Draggables.notify('onEnd', this, event);
|
||||||
|
|
||||||
|
var revert = this.options.revert;
|
||||||
|
if(revert && Object.isFunction(revert)) revert = revert(this.element);
|
||||||
|
|
||||||
|
var d = this.currentDelta();
|
||||||
|
if(revert && this.options.reverteffect) {
|
||||||
|
if (dropped == 0 || revert != 'failure')
|
||||||
|
this.options.reverteffect(this.element,
|
||||||
|
d[1]-this.delta[1], d[0]-this.delta[0]);
|
||||||
|
} else {
|
||||||
|
this.delta = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.options.zindex)
|
||||||
|
this.element.style.zIndex = this.originalZ;
|
||||||
|
|
||||||
|
if(this.options.endeffect)
|
||||||
|
this.options.endeffect(this.element);
|
||||||
|
|
||||||
|
Draggables.deactivate(this);
|
||||||
|
Droppables.reset();
|
||||||
|
},
|
||||||
|
|
||||||
|
keyPress: function(event) {
|
||||||
|
if(event.keyCode!=Event.KEY_ESC) return;
|
||||||
|
this.finishDrag(event, false);
|
||||||
|
Event.stop(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
endDrag: function(event) {
|
||||||
|
if(!this.dragging) return;
|
||||||
|
this.stopScrolling();
|
||||||
|
this.finishDrag(event, true);
|
||||||
|
Event.stop(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
draw: function(point) {
|
||||||
|
var pos = Position.cumulativeOffset(this.element);
|
||||||
|
if(this.options.ghosting) {
|
||||||
|
var r = Position.realOffset(this.element);
|
||||||
|
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
|
||||||
|
}
|
||||||
|
|
||||||
|
var d = this.currentDelta();
|
||||||
|
pos[0] -= d[0]; pos[1] -= d[1];
|
||||||
|
|
||||||
|
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
|
||||||
|
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
|
||||||
|
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = [0,1].map(function(i){
|
||||||
|
return (point[i]-pos[i]-this.offset[i])
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
if(this.options.snap) {
|
||||||
|
if(Object.isFunction(this.options.snap)) {
|
||||||
|
p = this.options.snap(p[0],p[1],this);
|
||||||
|
} else {
|
||||||
|
if(Object.isArray(this.options.snap)) {
|
||||||
|
p = p.map( function(v, i) {
|
||||||
|
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
|
||||||
|
} else {
|
||||||
|
p = p.map( function(v) {
|
||||||
|
return (v/this.options.snap).round()*this.options.snap }.bind(this))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
|
||||||
|
var style = this.element.style;
|
||||||
|
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
|
||||||
|
style.left = p[0] + "px";
|
||||||
|
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
||||||
|
style.top = p[1] + "px";
|
||||||
|
|
||||||
|
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
||||||
|
},
|
||||||
|
|
||||||
|
stopScrolling: function() {
|
||||||
|
if(this.scrollInterval) {
|
||||||
|
clearInterval(this.scrollInterval);
|
||||||
|
this.scrollInterval = null;
|
||||||
|
Draggables._lastScrollPointer = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startScrolling: function(speed) {
|
||||||
|
if(!(speed[0] || speed[1])) return;
|
||||||
|
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
|
||||||
|
this.lastScrolled = new Date();
|
||||||
|
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
|
||||||
|
},
|
||||||
|
|
||||||
|
scroll: function() {
|
||||||
|
var current = new Date();
|
||||||
|
var delta = current - this.lastScrolled;
|
||||||
|
this.lastScrolled = current;
|
||||||
|
if(this.options.scroll == window) {
|
||||||
|
with (this._getWindowScroll(this.options.scroll)) {
|
||||||
|
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
|
||||||
|
var d = delta / 1000;
|
||||||
|
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
|
||||||
|
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
Position.prepare();
|
||||||
|
Droppables.show(Draggables._lastPointer, this.element);
|
||||||
|
Draggables.notify('onDrag', this);
|
||||||
|
if (this._isScrollChild) {
|
||||||
|
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
|
||||||
|
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
|
||||||
|
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
|
||||||
|
if (Draggables._lastScrollPointer[0] < 0)
|
||||||
|
Draggables._lastScrollPointer[0] = 0;
|
||||||
|
if (Draggables._lastScrollPointer[1] < 0)
|
||||||
|
Draggables._lastScrollPointer[1] = 0;
|
||||||
|
this.draw(Draggables._lastScrollPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.options.change) this.options.change(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
_getWindowScroll: function(w) {
|
||||||
|
var T, L, W, H;
|
||||||
|
with (w.document) {
|
||||||
|
if (w.document.documentElement && documentElement.scrollTop) {
|
||||||
|
T = documentElement.scrollTop;
|
||||||
|
L = documentElement.scrollLeft;
|
||||||
|
} else if (w.document.body) {
|
||||||
|
T = body.scrollTop;
|
||||||
|
L = body.scrollLeft;
|
||||||
|
}
|
||||||
|
if (w.innerWidth) {
|
||||||
|
W = w.innerWidth;
|
||||||
|
H = w.innerHeight;
|
||||||
|
} else if (w.document.documentElement && documentElement.clientWidth) {
|
||||||
|
W = documentElement.clientWidth;
|
||||||
|
H = documentElement.clientHeight;
|
||||||
|
} else {
|
||||||
|
W = body.offsetWidth;
|
||||||
|
H = body.offsetHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { top: T, left: L, width: W, height: H };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Draggable._dragging = { };
|
||||||
|
|
||||||
|
/*--------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
var SortableObserver = Class.create({
|
||||||
|
initialize: function(element, observer) {
|
||||||
|
this.element = $(element);
|
||||||
|
this.observer = observer;
|
||||||
|
this.lastValue = Sortable.serialize(this.element);
|
||||||
|
},
|
||||||
|
|
||||||
|
onStart: function() {
|
||||||
|
this.lastValue = Sortable.serialize(this.element);
|
||||||
|
},
|
||||||
|
|
||||||
|
onEnd: function() {
|
||||||
|
Sortable.unmark();
|
||||||
|
if(this.lastValue != Sortable.serialize(this.element))
|
||||||
|
this.observer(this.element)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var Sortable = {
|
||||||
|
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
|
||||||
|
|
||||||
|
sortables: { },
|
||||||
|
|
||||||
|
_findRootElement: function(element) {
|
||||||
|
while (element.tagName.toUpperCase() != "BODY") {
|
||||||
|
if(element.id && Sortable.sortables[element.id]) return element;
|
||||||
|
element = element.parentNode;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
options: function(element) {
|
||||||
|
element = Sortable._findRootElement($(element));
|
||||||
|
if(!element) return;
|
||||||
|
return Sortable.sortables[element.id];
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function(element){
|
||||||
|
var s = Sortable.options(element);
|
||||||
|
|
||||||
|
if(s) {
|
||||||
|
Draggables.removeObserver(s.element);
|
||||||
|
s.droppables.each(function(d){ Droppables.remove(d) });
|
||||||
|
s.draggables.invoke('destroy');
|
||||||
|
|
||||||
|
delete Sortable.sortables[s.element.id];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
create: function(element) {
|
||||||
|
element = $(element);
|
||||||
|
var options = Object.extend({
|
||||||
|
element: element,
|
||||||
|
tag: 'li', // assumes li children, override with tag: 'tagname'
|
||||||
|
dropOnEmpty: false,
|
||||||
|
tree: false,
|
||||||
|
treeTag: 'ul',
|
||||||
|
overlap: 'vertical', // one of 'vertical', 'horizontal'
|
||||||
|
constraint: 'vertical', // one of 'vertical', 'horizontal', false
|
||||||
|
containment: element, // also takes array of elements (or id's); or false
|
||||||
|
handle: false, // or a CSS class
|
||||||
|
only: false,
|
||||||
|
delay: 0,
|
||||||
|
hoverclass: null,
|
||||||
|
ghosting: false,
|
||||||
|
quiet: false,
|
||||||
|
scroll: false,
|
||||||
|
scrollSensitivity: 20,
|
||||||
|
scrollSpeed: 15,
|
||||||
|
format: this.SERIALIZE_RULE,
|
||||||
|
|
||||||
|
// these take arrays of elements or ids and can be
|
||||||
|
// used for better initialization performance
|
||||||
|
elements: false,
|
||||||
|
handles: false,
|
||||||
|
|
||||||
|
onChange: Prototype.emptyFunction,
|
||||||
|
onUpdate: Prototype.emptyFunction
|
||||||
|
}, arguments[1] || { });
|
||||||
|
|
||||||
|
// clear any old sortable with same element
|
||||||
|
this.destroy(element);
|
||||||
|
|
||||||
|
// build options for the draggables
|
||||||
|
var options_for_draggable = {
|
||||||
|
revert: true,
|
||||||
|
quiet: options.quiet,
|
||||||
|
scroll: options.scroll,
|
||||||
|
scrollSpeed: options.scrollSpeed,
|
||||||
|
scrollSensitivity: options.scrollSensitivity,
|
||||||
|
delay: options.delay,
|
||||||
|
ghosting: options.ghosting,
|
||||||
|
constraint: options.constraint,
|
||||||
|
handle: options.handle };
|
||||||
|
|
||||||
|
if(options.starteffect)
|
||||||
|
options_for_draggable.starteffect = options.starteffect;
|
||||||
|
|
||||||
|
if(options.reverteffect)
|
||||||
|
options_for_draggable.reverteffect = options.reverteffect;
|
||||||
|
else
|
||||||
|
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
|
||||||
|
element.style.top = 0;
|
||||||
|
element.style.left = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
if(options.endeffect)
|
||||||
|
options_for_draggable.endeffect = options.endeffect;
|
||||||
|
|
||||||
|
if(options.zindex)
|
||||||
|
options_for_draggable.zindex = options.zindex;
|
||||||
|
|
||||||
|
// build options for the droppables
|
||||||
|
var options_for_droppable = {
|
||||||
|
overlap: options.overlap,
|
||||||
|
containment: options.containment,
|
||||||
|
tree: options.tree,
|
||||||
|
hoverclass: options.hoverclass,
|
||||||
|
onHover: Sortable.onHover
|
||||||
|
}
|
||||||
|
|
||||||
|
var options_for_tree = {
|
||||||
|
onHover: Sortable.onEmptyHover,
|
||||||
|
overlap: options.overlap,
|
||||||
|
containment: options.containment,
|
||||||
|
hoverclass: options.hoverclass
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix for gecko engine
|
||||||
|
Element.cleanWhitespace(element);
|
||||||
|
|
||||||
|
options.draggables = [];
|
||||||
|
options.droppables = [];
|
||||||
|
|
||||||
|
// drop on empty handling
|
||||||
|
if(options.dropOnEmpty || options.tree) {
|
||||||
|
Droppables.add(element, options_for_tree);
|
||||||
|
options.droppables.push(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
|
||||||
|
var handle = options.handles ? $(options.handles[i]) :
|
||||||
|
(options.handle ? $(e).select('.' + options.handle)[0] : e);
|
||||||
|
options.draggables.push(
|
||||||
|
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
|
||||||
|
Droppables.add(e, options_for_droppable);
|
||||||
|
if(options.tree) e.treeNode = element;
|
||||||
|
options.droppables.push(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(options.tree) {
|
||||||
|
(Sortable.findTreeElements(element, options) || []).each( function(e) {
|
||||||
|
Droppables.add(e, options_for_tree);
|
||||||
|
e.treeNode = element;
|
||||||
|
options.droppables.push(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep reference
|
||||||
|
this.sortables[element.id] = options;
|
||||||
|
|
||||||
|
// for onupdate
|
||||||
|
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
// return all suitable-for-sortable elements in a guaranteed order
|
||||||
|
findElements: function(element, options) {
|
||||||
|
return Element.findChildren(
|
||||||
|
element, options.only, options.tree ? true : false, options.tag);
|
||||||
|
},
|
||||||
|
|
||||||
|
findTreeElements: function(element, options) {
|
||||||
|
return Element.findChildren(
|
||||||
|
element, options.only, options.tree ? true : false, options.treeTag);
|
||||||
|
},
|
||||||
|
|
||||||
|
onHover: function(element, dropon, overlap) {
|
||||||
|
if(Element.isParent(dropon, element)) return;
|
||||||
|
|
||||||
|
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
|
||||||
|
return;
|
||||||
|
} else if(overlap>0.5) {
|
||||||
|
Sortable.mark(dropon, 'before');
|
||||||
|
if(dropon.previousSibling != element) {
|
||||||
|
var oldParentNode = element.parentNode;
|
||||||
|
element.style.visibility = "hidden"; // fix gecko rendering
|
||||||
|
dropon.parentNode.insertBefore(element, dropon);
|
||||||
|
if(dropon.parentNode!=oldParentNode)
|
||||||
|
Sortable.options(oldParentNode).onChange(element);
|
||||||
|
Sortable.options(dropon.parentNode).onChange(element);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Sortable.mark(dropon, 'after');
|
||||||
|
var nextElement = dropon.nextSibling || null;
|
||||||
|
if(nextElement != element) {
|
||||||
|
var oldParentNode = element.parentNode;
|
||||||
|
element.style.visibility = "hidden"; // fix gecko rendering
|
||||||
|
dropon.parentNode.insertBefore(element, nextElement);
|
||||||
|
if(dropon.parentNode!=oldParentNode)
|
||||||
|
Sortable.options(oldParentNode).onChange(element);
|
||||||
|
Sortable.options(dropon.parentNode).onChange(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onEmptyHover: function(element, dropon, overlap) {
|
||||||
|
var oldParentNode = element.parentNode;
|
||||||
|
var droponOptions = Sortable.options(dropon);
|
||||||
|
|
||||||
|
if(!Element.isParent(dropon, element)) {
|
||||||
|
var index;
|
||||||
|
|
||||||
|
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
|
||||||
|
var child = null;
|
||||||
|
|
||||||
|
if(children) {
|
||||||
|
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
|
||||||
|
|
||||||
|
for (index = 0; index < children.length; index += 1) {
|
||||||
|
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
|
||||||
|
offset -= Element.offsetSize (children[index], droponOptions.overlap);
|
||||||
|
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
|
||||||
|
child = index + 1 < children.length ? children[index + 1] : null;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
child = children[index];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dropon.insertBefore(element, child);
|
||||||
|
|
||||||
|
Sortable.options(oldParentNode).onChange(element);
|
||||||
|
droponOptions.onChange(element);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
unmark: function() {
|
||||||
|
if(Sortable._marker) Sortable._marker.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
mark: function(dropon, position) {
|
||||||
|
// mark on ghosting only
|
||||||
|
var sortable = Sortable.options(dropon.parentNode);
|
||||||
|
if(sortable && !sortable.ghosting) return;
|
||||||
|
|
||||||
|
if(!Sortable._marker) {
|
||||||
|
Sortable._marker =
|
||||||
|
($('dropmarker') || Element.extend(document.createElement('DIV'))).
|
||||||
|
hide().addClassName('dropmarker').setStyle({position:'absolute'});
|
||||||
|
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||||
|
}
|
||||||
|
var offsets = Position.cumulativeOffset(dropon);
|
||||||
|
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
|
||||||
|
|
||||||
|
if(position=='after')
|
||||||
|
if(sortable.overlap == 'horizontal')
|
||||||
|
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
|
||||||
|
else
|
||||||
|
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
|
||||||
|
|
||||||
|
Sortable._marker.show();
|
||||||
|
},
|
||||||
|
|
||||||
|
_tree: function(element, options, parent) {
|
||||||
|
var children = Sortable.findElements(element, options) || [];
|
||||||
|
|
||||||
|
for (var i = 0; i < children.length; ++i) {
|
||||||
|
var match = children[i].id.match(options.format);
|
||||||
|
|
||||||
|
if (!match) continue;
|
||||||
|
|
||||||
|
var child = {
|
||||||
|
id: encodeURIComponent(match ? match[1] : null),
|
||||||
|
element: element,
|
||||||
|
parent: parent,
|
||||||
|
children: [],
|
||||||
|
position: parent.children.length,
|
||||||
|
container: $(children[i]).down(options.treeTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the element containing the children and recurse over it */
|
||||||
|
if (child.container)
|
||||||
|
this._tree(child.container, options, child)
|
||||||
|
|
||||||
|
parent.children.push (child);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent;
|
||||||
|
},
|
||||||
|
|
||||||
|
tree: function(element) {
|
||||||
|
element = $(element);
|
||||||
|
var sortableOptions = this.options(element);
|
||||||
|
var options = Object.extend({
|
||||||
|
tag: sortableOptions.tag,
|
||||||
|
treeTag: sortableOptions.treeTag,
|
||||||
|
only: sortableOptions.only,
|
||||||
|
name: element.id,
|
||||||
|
format: sortableOptions.format
|
||||||
|
}, arguments[1] || { });
|
||||||
|
|
||||||
|
var root = {
|
||||||
|
id: null,
|
||||||
|
parent: null,
|
||||||
|
children: [],
|
||||||
|
container: element,
|
||||||
|
position: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sortable._tree(element, options, root);
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Construct a [i] index for a particular node */
|
||||||
|
_constructIndex: function(node) {
|
||||||
|
var index = '';
|
||||||
|
do {
|
||||||
|
if (node.id) index = '[' + node.position + ']' + index;
|
||||||
|
} while ((node = node.parent) != null);
|
||||||
|
return index;
|
||||||
|
},
|
||||||
|
|
||||||
|
sequence: function(element) {
|
||||||
|
element = $(element);
|
||||||
|
var options = Object.extend(this.options(element), arguments[1] || { });
|
||||||
|
|
||||||
|
return $(this.findElements(element, options) || []).map( function(item) {
|
||||||
|
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setSequence: function(element, new_sequence) {
|
||||||
|
element = $(element);
|
||||||
|
var options = Object.extend(this.options(element), arguments[2] || { });
|
||||||
|
|
||||||
|
var nodeMap = { };
|
||||||
|
this.findElements(element, options).each( function(n) {
|
||||||
|
if (n.id.match(options.format))
|
||||||
|
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
|
||||||
|
n.parentNode.removeChild(n);
|
||||||
|
});
|
||||||
|
|
||||||
|
new_sequence.each(function(ident) {
|
||||||
|
var n = nodeMap[ident];
|
||||||
|
if (n) {
|
||||||
|
n[1].appendChild(n[0]);
|
||||||
|
delete nodeMap[ident];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
serialize: function(element) {
|
||||||
|
element = $(element);
|
||||||
|
var options = Object.extend(Sortable.options(element), arguments[1] || { });
|
||||||
|
var name = encodeURIComponent(
|
||||||
|
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
|
||||||
|
|
||||||
|
if (options.tree) {
|
||||||
|
return Sortable.tree(element, arguments[1]).children.map( function (item) {
|
||||||
|
return [name + Sortable._constructIndex(item) + "[id]=" +
|
||||||
|
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
|
||||||
|
}).flatten().join('&');
|
||||||
|
} else {
|
||||||
|
return Sortable.sequence(element, arguments[1]).map( function(item) {
|
||||||
|
return name + "[]=" + encodeURIComponent(item);
|
||||||
|
}).join('&');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if child is contained within element
|
||||||
|
Element.isParent = function(child, element) {
|
||||||
|
if (!child.parentNode || child == element) return false;
|
||||||
|
if (child.parentNode == element) return true;
|
||||||
|
return Element.isParent(child.parentNode, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
Element.findChildren = function(element, only, recursive, tagName) {
|
||||||
|
if(!element.hasChildNodes()) return null;
|
||||||
|
tagName = tagName.toUpperCase();
|
||||||
|
if(only) only = [only].flatten();
|
||||||
|
var elements = [];
|
||||||
|
$A(element.childNodes).each( function(e) {
|
||||||
|
if(e.tagName && e.tagName.toUpperCase()==tagName &&
|
||||||
|
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
|
||||||
|
elements.push(e);
|
||||||
|
if(recursive) {
|
||||||
|
var grandchildren = Element.findChildren(e, only, recursive, tagName);
|
||||||
|
if(grandchildren) elements.push(grandchildren);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (elements.length>0 ? elements.flatten() : []);
|
||||||
|
}
|
||||||
|
|
||||||
|
Element.offsetSize = function (element, type) {
|
||||||
|
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
background-color: #e8e8e8;
|
||||||
|
text-align: center;
|
||||||
|
font: 14px Trebuchet MS, Lucida Sans Unicode, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a img {
|
||||||
|
border: none;
|
||||||
|
vertical-align: bottom;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, a:visited, a:hover {
|
||||||
|
color: #c10808;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
width: 500px;
|
||||||
|
margin-left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 750px;
|
||||||
|
position: relative;
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 0px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#flash_container {
|
||||||
|
padding: 5px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#flash_container div {
|
||||||
|
padding: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
background-color: #edd400;
|
||||||
|
border-top: 1px solid #c4a000;
|
||||||
|
border-bottom: 1px solid #c4a000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background-color: #ff6767;
|
||||||
|
border-top: 1px solid #a40000;
|
||||||
|
border-bottom: 1px solid #a40000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
height: 120px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header_image {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tool_bar {
|
||||||
|
margin-top: 7px;
|
||||||
|
padding: 3px 10px 3px 10px;
|
||||||
|
border-top: 1px solid #c17d11;
|
||||||
|
border-bottom: 1px solid #c17d11;
|
||||||
|
background-color: #e9b96e;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tool_bar a {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
padding-left: 3px;
|
||||||
|
padding-right: 3px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #8f5902;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tool_bar a:hover {
|
||||||
|
color: #5e3a01;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content a:hover {
|
||||||
|
color: #204a87;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
font-size: 12px;
|
||||||
|
height: 25px;
|
||||||
|
width: 90%;
|
||||||
|
border-top: 2px solid #e8e8e8;
|
||||||
|
text-align: right;
|
||||||
|
padding: 5px 0 10px 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no_list_style {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#recaptcha_container {
|
||||||
|
margin: 10px 0px 10px 0px;
|
|
@ -0,0 +1,119 @@
|
||||||
|
body {
|
||||||
|
font-family: Arial, Verdana, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
#container {
|
||||||
|
width: 80%;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #fff;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
#header-container {
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
#header-container h1, #header-container h2 {
|
||||||
|
margin-left: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.spacer {
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
border: 0px;
|
||||||
|
color: #ccc;
|
||||||
|
background-color: #cdcdcd;
|
||||||
|
height: 1px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #c55;
|
||||||
|
background-color: #fff;
|
||||||
|
font-family: Arial, Verdana, sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #999;
|
||||||
|
font-family: Arial, Verdana, sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
color: #4d9b12;
|
||||||
|
font-size: 15px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 300;
|
||||||
|
padding: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#left-container {
|
||||||
|
float: left;
|
||||||
|
width: 250px;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#left-container h3 {
|
||||||
|
color: #c55;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-container {
|
||||||
|
margin: 5px 5px 5px 260px;
|
||||||
|
padding: 15px;
|
||||||
|
border-left: 1px solid silver;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #4d9b12;
|
||||||
|
background-color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #4d9b12;
|
||||||
|
background-color: #fff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
#footer-container {
|
||||||
|
clear: both;
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: Verdana, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
.right {
|
||||||
|
float: right;
|
||||||
|
font-size: 100%;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: #999;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.left {
|
||||||
|
float: left;
|
||||||
|
font-size: 100%;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: #999;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
#main-container ul {
|
||||||
|
margin-left: 3.0em;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
class CreateUsersMigration < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :users do |t|
|
||||||
|
t.string :user_name, :limit => 32
|
||||||
|
t.string :auth_token
|
||||||
|
t.boolean :administrator
|
||||||
|
t.datetime :created_at
|
||||||
|
end
|
||||||
|
add_index :users, :user_name
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :users
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,16 @@
|
||||||
|
class CreatePhotosMigration < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :photos do |t|
|
||||||
|
t.string :filename
|
||||||
|
t.integer :user_id
|
||||||
|
t.string :owner_token
|
||||||
|
t.datetime :created_at
|
||||||
|
end
|
||||||
|
add_index :photos, :user_id
|
||||||
|
add_index :photos, :owner_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :photos
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
class CreateVotesMigration < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :votes do |t|
|
||||||
|
t.integer :photo_id, :user_id
|
||||||
|
t.boolean :vote
|
||||||
|
end
|
||||||
|
add_index :votes, :photo_id
|
||||||
|
add_index :votes, :user_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :votes
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
class CreateSessionsMigration < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :sessions do |t|
|
||||||
|
t.column :session_id, :string
|
||||||
|
t.column :data, :text
|
||||||
|
t.column :updated_at, :datetime
|
||||||
|
end
|
||||||
|
add_index :sessions, :session_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :sessions
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,50 @@
|
||||||
|
# This file is auto-generated from the current state of the database. Instead of editing this file,
|
||||||
|
# please use the migrations feature of Active Record to incrementally modify your database, and
|
||||||
|
# then regenerate this schema definition.
|
||||||
|
#
|
||||||
|
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
|
||||||
|
# to create the application database on another system, you should be using db:schema:load, not running
|
||||||
|
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
||||||
|
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
||||||
|
#
|
||||||
|
# It's strongly recommended to check this file into your version control system.
|
||||||
|
|
||||||
|
ActiveRecord::Schema.define(:version => 4) do
|
||||||
|
|
||||||
|
create_table "photos", :force => true do |t|
|
||||||
|
t.string "filename"
|
||||||
|
t.integer "user_id"
|
||||||
|
t.string "owner_token"
|
||||||
|
t.datetime "created_at"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "photos", ["owner_token"], :name => "index_photos_on_owner_token"
|
||||||
|
add_index "photos", ["user_id"], :name => "index_photos_on_user_id"
|
||||||
|
|
||||||
|
create_table "sessions", :force => true do |t|
|
||||||
|
t.string "session_id"
|
||||||
|
t.text "data"
|
||||||
|
t.datetime "updated_at"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
|
||||||
|
|
||||||
|
create_table "users", :force => true do |t|
|
||||||
|
t.string "user_name", :limit => 32
|
||||||
|
t.string "auth_token"
|
||||||
|
t.boolean "administrator"
|
||||||
|
t.datetime "created_at"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "users", ["user_name"], :name => "index_users_on_user_name"
|
||||||
|
|
||||||
|
create_table "votes", :force => true do |t|
|
||||||
|
t.integer "photo_id"
|
||||||
|
t.integer "user_id"
|
||||||
|
t.boolean "vote"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "votes", ["user_id"], :name => "index_votes_on_user_id"
|
||||||
|
add_index "votes", ["photo_id"], :name => "index_votes_on_photo_id"
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Favorites, "index action" do
|
||||||
|
before(:each) do
|
||||||
|
dispatch_to(Favorites, :index)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Home, "index action" do
|
||||||
|
before(:each) do
|
||||||
|
dispatch_to(Home, :index)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Photos, "index action" do
|
||||||
|
before(:each) do
|
||||||
|
dispatch_to(Photos, :index)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Sessions, "index action" do
|
||||||
|
before(:each) do
|
||||||
|
dispatch_to(Sessions, :index)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Stats, "index action" do
|
||||||
|
before(:each) do
|
||||||
|
dispatch_to(Stats, :index)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Users, "index action" do
|
||||||
|
before(:each) do
|
||||||
|
dispatch_to(Users, :index)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Votes, "index action" do
|
||||||
|
before(:each) do
|
||||||
|
dispatch_to(Votes, :index)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Merb::FavoritesHelper do
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Merb::HomeHelper do
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Merb::PhotosHelper do
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Merb::SessionsHelper do
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Merb::StatsHelper do
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Merb::UsersHelper do
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||||
|
|
||||||
|
describe Merb::VotesHelper do
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join( File.dirname(__FILE__), '..', "spec_helper" )
|
||||||
|
|
||||||
|
describe Photo do
|
||||||
|
|
||||||
|
it "should have specs"
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join( File.dirname(__FILE__), '..', "spec_helper" )
|
||||||
|
|
||||||
|
describe User do
|
||||||
|
|
||||||
|
it "should have specs"
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join( File.dirname(__FILE__), '..', "spec_helper" )
|
||||||
|
|
||||||
|
describe Vote do
|
||||||
|
|
||||||
|
it "should have specs"
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,13 @@
|
||||||
|
require 'rubygems'
|
||||||
|
require 'merb-core'
|
||||||
|
require 'spec' # Satisfies Autotest and anyone else not using the Rake tasks
|
||||||
|
|
||||||
|
# this loads all plugins required in your init file so don't add them
|
||||||
|
# here again, Merb will do it for you
|
||||||
|
Merb.start_environment(:testing => true, :adapter => 'runner', :environment => ENV['MERB_ENV'] || 'test')
|
||||||
|
|
||||||
|
Spec::Runner.configure do |config|
|
||||||
|
config.include(Merb::Test::ViewHelper)
|
||||||
|
config.include(Merb::Test::RouteHelper)
|
||||||
|
config.include(Merb::Test::ControllerHelper)
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
$TESTING=true
|
||||||
|
require 'rubygems'
|
||||||
|
require 'merb-core'
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Boot Merb, via the Test Rack adapter
|
||||||
|
Merb.start :environment => (ENV['MERB_ENV'] || 'test'),
|
||||||
|
:merb_root => File.join(File.dirname(__FILE__), ".." )
|
||||||
|
|
||||||
|
|
||||||
|
class Test::Unit::TestCase
|
||||||
|
include Merb::Test::RequestHelper
|
||||||
|
# Add more helper methods to be used by all tests here...
|
||||||
|
end
|