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
|