add rails backend scaffold to new example

This commit is contained in:
Jeremy Ellison
2011-01-07 11:56:07 -05:00
parent a4c3fc9dae
commit ddd5d52c4a
295 changed files with 16514 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
.bundle
db/*.sqlite3
log/*.log
tmp/**/*

View File

@@ -0,0 +1,35 @@
source 'http://rubygems.org'
gem 'rails', '3.0.3'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'sqlite3-ruby', :require => 'sqlite3'
# Use unicorn as the web server
# gem 'unicorn'
# Deploy with Capistrano
# gem 'capistrano'
# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
# gem 'ruby-debug'
# gem 'ruby-debug19'
# Bundle the extra gems:
# gem 'bj'
# gem 'nokogiri'
# gem 'sqlite3-ruby', :require => 'sqlite3'
# gem 'aws-s3', :require => 'aws/s3'
# Bundle gems for the local environment. Make sure to
# put test-only gems in this group so their generators
# and rake tasks are available in development mode:
# group :development, :test do
# gem 'webrat'
# end
source "http://rubygems.org"
gem 'authlogic', :require => 'authlogic'
gem 'paperclip'

View File

@@ -0,0 +1,81 @@
GEM
remote: http://rubygems.org/
remote: http://rubygems.org/
specs:
abstract (1.0.0)
actionmailer (3.0.3)
actionpack (= 3.0.3)
mail (~> 2.2.9)
actionpack (3.0.3)
activemodel (= 3.0.3)
activesupport (= 3.0.3)
builder (~> 2.1.2)
erubis (~> 2.6.6)
i18n (~> 0.4)
rack (~> 1.2.1)
rack-mount (~> 0.6.13)
rack-test (~> 0.5.6)
tzinfo (~> 0.3.23)
activemodel (3.0.3)
activesupport (= 3.0.3)
builder (~> 2.1.2)
i18n (~> 0.4)
activerecord (3.0.3)
activemodel (= 3.0.3)
activesupport (= 3.0.3)
arel (~> 2.0.2)
tzinfo (~> 0.3.23)
activeresource (3.0.3)
activemodel (= 3.0.3)
activesupport (= 3.0.3)
activesupport (3.0.3)
arel (2.0.6)
authlogic (2.1.6)
activesupport
builder (2.1.2)
erubis (2.6.6)
abstract (>= 1.0.0)
i18n (0.5.0)
mail (2.2.14)
activesupport (>= 2.3.6)
i18n (>= 0.4.0)
mime-types (~> 1.16)
treetop (~> 1.4.8)
mime-types (1.16)
paperclip (2.3.8)
activerecord
activesupport
polyglot (0.3.1)
rack (1.2.1)
rack-mount (0.6.13)
rack (>= 1.0.0)
rack-test (0.5.7)
rack (>= 1.0)
rails (3.0.3)
actionmailer (= 3.0.3)
actionpack (= 3.0.3)
activerecord (= 3.0.3)
activeresource (= 3.0.3)
activesupport (= 3.0.3)
bundler (~> 1.0)
railties (= 3.0.3)
railties (3.0.3)
actionpack (= 3.0.3)
activesupport (= 3.0.3)
rake (>= 0.8.7)
thor (~> 0.14.4)
rake (0.8.7)
sqlite3-ruby (1.3.2)
thor (0.14.6)
treetop (1.4.9)
polyglot (>= 0.3.1)
tzinfo (0.3.23)
PLATFORMS
ruby
DEPENDENCIES
authlogic
paperclip
rails (= 3.0.3)
sqlite3-ruby

View File

@@ -0,0 +1,256 @@
== Welcome to Rails
Rails is a web-application framework that includes everything needed to create
database-backed web applications according to the Model-View-Control pattern.
This pattern splits the view (also called the presentation) into "dumb"
templates that are primarily responsible for inserting pre-built data in between
HTML tags. The model contains the "smart" domain objects (such as Account,
Product, Person, Post) that holds all the business logic and knows how to
persist themselves to a database. The controller handles the incoming requests
(such as Save New Account, Update Product, Show Post) by manipulating the model
and directing data to the view.
In Rails, the model is handled by what's called an object-relational mapping
layer entitled Active Record. This layer allows you to present the data from
database rows as objects and embellish these data objects with business logic
methods. You can read more about Active Record in
link:files/vendor/rails/activerecord/README.html.
The controller and view are handled by the Action Pack, which handles both
layers by its two parts: Action View and Action Controller. These two layers
are bundled in a single package due to their heavy interdependence. This is
unlike the relationship between the Active Record and Action Pack that is much
more separate. Each of these packages can be used independently outside of
Rails. You can read more about Action Pack in
link:files/vendor/rails/actionpack/README.html.
== Getting Started
1. At the command prompt, create a new Rails application:
<tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
2. Change directory to <tt>myapp</tt> and start the web server:
<tt>cd myapp; rails server</tt> (run with --help for options)
3. Go to http://localhost:3000/ and you'll see:
"Welcome aboard: You're riding Ruby on Rails!"
4. Follow the guidelines to start developing your application. You can find
the following resources handy:
* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
== Debugging Rails
Sometimes your application goes wrong. Fortunately there are a lot of tools that
will help you debug it and get it back on the rails.
First area to check is the application log files. Have "tail -f" commands
running on the server.log and development.log. Rails will automatically display
debugging and runtime information to these files. Debugging info will also be
shown in the browser on requests from 127.0.0.1.
You can also log your own messages directly into the log file from your code
using the Ruby logger class from inside your controllers. Example:
class WeblogController < ActionController::Base
def destroy
@weblog = Weblog.find(params[:id])
@weblog.destroy
logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
end
end
The result will be a message in your log file along the lines of:
Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
More information on how to use the logger is at http://www.ruby-doc.org/core/
Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
several books available online as well:
* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
These two books will bring you up to speed on the Ruby language and also on
programming in general.
== Debugger
Debugger support is available through the debugger command when you start your
Mongrel or WEBrick server with --debugger. This means that you can break out of
execution at any point in the code, investigate and change the model, and then,
resume execution! You need to install ruby-debug to run the server in debugging
mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
class WeblogController < ActionController::Base
def index
@posts = Post.find(:all)
debugger
end
end
So the controller will accept the action, run the first line, then present you
with a IRB prompt in the server window. Here you can do things like:
>> @posts.inspect
=> "[#<Post:0x14a6be8
@attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
#<Post:0x14a6620
@attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
>> @posts.first.title = "hello from a debugger"
=> "hello from a debugger"
...and even better, you can examine how your runtime objects actually work:
>> f = @posts.first
=> #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
>> f.
Display all 152 possibilities? (y or n)
Finally, when you're ready to resume execution, you can enter "cont".
== Console
The console is a Ruby shell, which allows you to interact with your
application's domain model. Here you'll have all parts of the application
configured, just like it is when the application is running. You can inspect
domain models, change values, and save to the database. Starting the script
without arguments will launch it in the development environment.
To start the console, run <tt>rails console</tt> from the application
directory.
Options:
* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications
made to the database.
* Passing an environment name as an argument will load the corresponding
environment. Example: <tt>rails console production</tt>.
To reload your controllers and models after launching the console run
<tt>reload!</tt>
More information about irb can be found at:
link:http://www.rubycentral.com/pickaxe/irb.html
== dbconsole
You can go to the command line of your database directly through <tt>rails
dbconsole</tt>. You would be connected to the database with the credentials
defined in database.yml. Starting the script without arguments will connect you
to the development database. Passing an argument will connect you to a different
database, like <tt>rails dbconsole production</tt>. Currently works for MySQL,
PostgreSQL and SQLite 3.
== Description of Contents
The default directory structure of a generated Ruby on Rails application:
|-- app
| |-- controllers
| |-- helpers
| |-- mailers
| |-- models
| `-- views
| `-- layouts
|-- config
| |-- environments
| |-- initializers
| `-- locales
|-- db
|-- doc
|-- lib
| `-- tasks
|-- log
|-- public
| |-- images
| |-- javascripts
| `-- stylesheets
|-- script
|-- test
| |-- fixtures
| |-- functional
| |-- integration
| |-- performance
| `-- unit
|-- tmp
| |-- cache
| |-- pids
| |-- sessions
| `-- sockets
`-- vendor
`-- plugins
app
Holds all the code that's specific to this particular application.
app/controllers
Holds controllers that should be named like weblogs_controller.rb for
automated URL mapping. All controllers should descend from
ApplicationController which itself descends from ActionController::Base.
app/models
Holds models that should be named like post.rb. Models descend from
ActiveRecord::Base by default.
app/views
Holds the template files for the view that should be named like
weblogs/index.html.erb for the WeblogsController#index action. All views use
eRuby syntax by default.
app/views/layouts
Holds the template files for layouts to be used with views. This models the
common header/footer method of wrapping views. In your views, define a layout
using the <tt>layout :default</tt> and create a file named default.html.erb.
Inside default.html.erb, call <% yield %> to render the view using this
layout.
app/helpers
Holds view helpers that should be named like weblogs_helper.rb. These are
generated for you automatically when using generators for controllers.
Helpers can be used to wrap functionality for your views into methods.
config
Configuration files for the Rails environment, the routing map, the database,
and other dependencies.
db
Contains the database schema in schema.rb. db/migrate contains all the
sequence of Migrations for your schema.
doc
This directory is where your application documentation will be stored when
generated using <tt>rake doc:app</tt>
lib
Application specific libraries. Basically, any kind of custom code that
doesn't belong under controllers, models, or helpers. This directory is in
the load path.
public
The directory available for the web server. Contains subdirectories for
images, stylesheets, and javascripts. Also contains the dispatchers and the
default HTML files. This should be set as the DOCUMENT_ROOT of your web
server.
script
Helper scripts for automation and generation.
test
Unit and functional tests along with fixtures. When using the rails generate
command, template test files will be generated for you and placed in this
directory.
vendor
External libraries that the application depends on. Also includes the plugins
subdirectory. If the app has frozen rails, those gems also go here, under
vendor/rails/. This directory is in the load path.

View File

@@ -0,0 +1,7 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__)
require 'rake'
DiscussionBoardBackend::Application.load_tasks

View File

@@ -0,0 +1,3 @@
class ApplicationController < ActionController::Base
protect_from_forgery
end

View File

@@ -0,0 +1,24 @@
class UsersController < ApplicationController
protect_from_forgery :except => [:login, :signup]
def login
# this method logs you in and returns you a single_access_token token for authentication.
user_session = UserSession.new(params)
if user_session.save
user = user_session.user
render :json => {:login => user.login, :single_access_token => user.single_access_token}
else
render :json => {:error => "Invalid Login"}
end
end
def signup
user = User.new(params[:user])
if user.save
render :json => {:login => user.login, :single_access_token => user.single_access_token}
else
render :json => {:errors => user.errors.full_messages}
end
end
end

View File

@@ -0,0 +1,2 @@
module ApplicationHelper
end

View File

@@ -0,0 +1,2 @@
module LoginsControllerHelper
end

View File

@@ -0,0 +1,4 @@
class User < ActiveRecord::Base
acts_as_authentic
end

View File

@@ -0,0 +1,5 @@
class UserSession < Authlogic::Session::Base
# specify configuration here, such as:
# logout_on_timeout true
# ...many more options in the documentation
end

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>DiscussionBoardBackend</title>
<%= stylesheet_link_tag :all %>
<%= javascript_include_tag :defaults %>
<%= csrf_meta_tag %>
</head>
<body>
<%= yield %>
</body>
</html>

View File

@@ -0,0 +1,4 @@
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
run DiscussionBoardBackend::Application

View File

@@ -0,0 +1,42 @@
require File.expand_path('../boot', __FILE__)
require 'rails/all'
# If you have a Gemfile, require the gems listed there, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(:default, Rails.env) if defined?(Bundler)
module DiscussionBoardBackend
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
# JavaScript files you want as :defaults (application.js is always included).
# config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
end
end

View File

@@ -0,0 +1,13 @@
require 'rubygems'
# Set up gems listed in the Gemfile.
gemfile = File.expand_path('../../Gemfile', __FILE__)
begin
ENV['BUNDLE_GEMFILE'] = gemfile
require 'bundler'
Bundler.setup
rescue Bundler::GemNotFound => e
STDERR.puts e.message
STDERR.puts "Try running `bundle install`."
exit!
end if File.exist?(gemfile)

View File

@@ -0,0 +1,22 @@
# SQLite version 3.x
# gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
adapter: sqlite3
database: db/development.sqlite3
pool: 5
timeout: 5000
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: sqlite3
database: db/test.sqlite3
pool: 5
timeout: 5000
production:
adapter: sqlite3
database: db/production.sqlite3
pool: 5
timeout: 5000

View File

@@ -0,0 +1,5 @@
# Load the rails application
require File.expand_path('../application', __FILE__)
# Initialize the rails application
DiscussionBoardBackend::Application.initialize!

View File

@@ -0,0 +1,26 @@
DiscussionBoardBackend::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = false
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_view.debug_rjs = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
# Only use best-standards-support built into browsers
config.action_dispatch.best_standards_support = :builtin
end

View File

@@ -0,0 +1,49 @@
DiscussionBoardBackend::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# The production environment is meant for finished, "live" apps.
# Code is not reloaded between requests
config.cache_classes = true
# Full error reports are disabled and caching is turned on
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Specifies the header that your server uses for sending files
config.action_dispatch.x_sendfile_header = "X-Sendfile"
# For nginx:
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
# If you have no front-end server that supports something like X-Sendfile,
# just comment this out and Rails will serve the files
# See everything in the log (default is :info)
# config.log_level = :debug
# Use a different logger for distributed setups
# config.logger = SyslogLogger.new
# Use a different cache store in production
# config.cache_store = :mem_cache_store
# Disable Rails's static asset server
# In production, Apache or nginx will already do this
config.serve_static_assets = false
# Enable serving of images, stylesheets, and javascripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
# Enable threaded mode
# config.threadsafe!
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners
config.active_support.deprecation = :notify
end

View File

@@ -0,0 +1,35 @@
DiscussionBoardBackend::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Raise exceptions instead of rendering exception templates
config.action_dispatch.show_exceptions = false
# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
end

View File

@@ -0,0 +1,7 @@
# Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
# Rails.backtrace_cleaner.remove_silencers!

View File

@@ -0,0 +1,10 @@
# Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format
# (all these examples are active by default):
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end

View File

@@ -0,0 +1,5 @@
# Be sure to restart your server when you modify this file.
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
# Mime::Type.register_alias "text/html", :iphone

View File

@@ -0,0 +1,7 @@
# Be sure to restart your server when you modify this file.
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
DiscussionBoardBackend::Application.config.secret_token = '616d0e680e77902eb11864b67ca2d0dc1d521a3801fbb30f46de4bcd5598423142000c31f470f1e35335b692e0a971c754c98ee153ed323f516c190eff38dae4'

View File

@@ -0,0 +1,8 @@
# Be sure to restart your server when you modify this file.
DiscussionBoardBackend::Application.config.session_store :cookie_store, :key => '_discussion_board_backend_session'
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rails generate session_migration")
# DiscussionBoardBackend::Application.config.session_store :active_record_store

View File

@@ -0,0 +1,5 @@
# Sample localization file for English. Add more files in this directory for other locales.
# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
en:
hello: "Hello world"

View File

@@ -0,0 +1,61 @@
DiscussionBoardBackend::Application.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
match 'login' => 'users#login'
match 'signup' => 'users#signup'
# Sample of regular route:
# match 'products/:id' => 'catalog#view'
# Keep in mind you can assign values other than :controller and :action
# Sample of named route:
# match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
# This route can be invoked with purchase_url(:id => product.id)
# Sample resource route (maps HTTP verbs to controller actions automatically):
# resources :products
# Sample resource route with options:
# resources :products do
# member do
# get 'short'
# post 'toggle'
# end
#
# collection do
# get 'sold'
# end
# end
# Sample resource route with sub-resources:
# resources :products do
# resources :comments, :sales
# resource :seller
# end
# Sample resource route with more complex sub-resources
# resources :products do
# resources :comments
# resources :sales do
# get 'recent', :on => :collection
# end
# end
# Sample resource route within a namespace:
# namespace :admin do
# # Directs /admin/products/* to Admin::ProductsController
# # (app/controllers/admin/products_controller.rb)
# resources :products
# end
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
# root :to => "welcome#index"
# See how all your routes lay out with "rake routes"
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
# match ':controller(/:action(/:id(.:format)))'
end

View File

@@ -0,0 +1,28 @@
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :login, :null => false # optional, you can use email instead, or both
t.string :email, :null => false # optional, you can use login instead, or both
t.string :crypted_password, :null => false # optional, see below
t.string :password_salt, :null => false # optional, but highly recommended
t.string :persistence_token, :null => false # required
t.string :single_access_token, :null => false # optional, see Authlogic::Session::Params
t.string :perishable_token, :null => false # optional, see Authlogic::Session::Perishability
# Magic columns, just like ActiveRecord's created_at and updated_at. These are automatically maintained by Authlogic if they are present.
t.integer :login_count, :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
t.integer :failed_login_count, :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
t.datetime :last_request_at # optional, see Authlogic::Session::MagicColumns
t.datetime :current_login_at # optional, see Authlogic::Session::MagicColumns
t.datetime :last_login_at # optional, see Authlogic::Session::MagicColumns
t.string :current_login_ip # optional, see Authlogic::Session::MagicColumns
t.string :last_login_ip # optional, see Authlogic::Session::MagicColumn
t.timestamps
end
end
def self.down
drop_table :users
end
end

View File

@@ -0,0 +1,34 @@
# 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 => 20110107162223) do
create_table "users", :force => true do |t|
t.string "login", :null => false
t.string "email", :null => false
t.string "crypted_password", :null => false
t.string "password_salt", :null => false
t.string "persistence_token", :null => false
t.string "single_access_token", :null => false
t.string "perishable_token", :null => false
t.integer "login_count", :default => 0, :null => false
t.integer "failed_login_count", :default => 0, :null => false
t.datetime "last_request_at"
t.datetime "current_login_at"
t.datetime "last_login_at"
t.string "current_login_ip"
t.string "last_login_ip"
t.datetime "created_at"
t.datetime "updated_at"
end
end

View File

@@ -0,0 +1,7 @@
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
# Mayor.create(:name => 'Daley', :city => cities.first)

View File

@@ -0,0 +1,2 @@
Use this README file to introduce your application and point to useful places in the API for learning more.
Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/404.html -->
<div class="dialog">
<h1>The page you were looking for doesn't exist.</h1>
<p>You may have mistyped the address or the page may have moved.</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>The change you wanted was rejected (422)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/422.html -->
<div class="dialog">
<h1>The change you wanted was rejected.</h1>
<p>Maybe you tried to change something you didn't have access to.</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/500.html -->
<div class="dialog">
<h1>We're sorry, but something went wrong.</h1>
<p>We've been notified about this issue and we'll take a look at it shortly.</p>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -0,0 +1,239 @@
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails: Welcome aboard</title>
<style type="text/css" media="screen">
body {
margin: 0;
margin-bottom: 25px;
padding: 0;
background-color: #f0f0f0;
font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
font-size: 13px;
color: #333;
}
h1 {
font-size: 28px;
color: #000;
}
a {color: #03c}
a:hover {
background-color: #03c;
color: white;
text-decoration: none;
}
#page {
background-color: #f0f0f0;
width: 750px;
margin: 0;
margin-left: auto;
margin-right: auto;
}
#content {
float: left;
background-color: white;
border: 3px solid #aaa;
border-top: none;
padding: 25px;
width: 500px;
}
#sidebar {
float: right;
width: 175px;
}
#footer {
clear: both;
}
#header, #about, #getting-started {
padding-left: 75px;
padding-right: 30px;
}
#header {
background-image: url("images/rails.png");
background-repeat: no-repeat;
background-position: top left;
height: 64px;
}
#header h1, #header h2 {margin: 0}
#header h2 {
color: #888;
font-weight: normal;
font-size: 16px;
}
#about h3 {
margin: 0;
margin-bottom: 10px;
font-size: 14px;
}
#about-content {
background-color: #ffd;
border: 1px solid #fc0;
margin-left: -55px;
margin-right: -10px;
}
#about-content table {
margin-top: 10px;
margin-bottom: 10px;
font-size: 11px;
border-collapse: collapse;
}
#about-content td {
padding: 10px;
padding-top: 3px;
padding-bottom: 3px;
}
#about-content td.name {color: #555}
#about-content td.value {color: #000}
#about-content ul {
padding: 0;
list-style-type: none;
}
#about-content.failure {
background-color: #fcc;
border: 1px solid #f00;
}
#about-content.failure p {
margin: 0;
padding: 10px;
}
#getting-started {
border-top: 1px solid #ccc;
margin-top: 25px;
padding-top: 15px;
}
#getting-started h1 {
margin: 0;
font-size: 20px;
}
#getting-started h2 {
margin: 0;
font-size: 14px;
font-weight: normal;
color: #333;
margin-bottom: 25px;
}
#getting-started ol {
margin-left: 0;
padding-left: 0;
}
#getting-started li {
font-size: 18px;
color: #888;
margin-bottom: 25px;
}
#getting-started li h2 {
margin: 0;
font-weight: normal;
font-size: 18px;
color: #333;
}
#getting-started li p {
color: #555;
font-size: 13px;
}
#sidebar ul {
margin-left: 0;
padding-left: 0;
}
#sidebar ul h3 {
margin-top: 25px;
font-size: 16px;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
}
#sidebar li {
list-style-type: none;
}
#sidebar ul.links li {
margin-bottom: 5px;
}
</style>
<script type="text/javascript">
function about() {
info = document.getElementById('about-content');
if (window.XMLHttpRequest)
{ xhr = new XMLHttpRequest(); }
else
{ xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
xhr.open("GET","rails/info/properties",false);
xhr.send("");
info.innerHTML = xhr.responseText;
info.style.display = 'block'
}
</script>
</head>
<body>
<div id="page">
<div id="sidebar">
<ul id="sidebar-items">
<li>
<h3>Browse the documentation</h3>
<ul class="links">
<li><a href="http://api.rubyonrails.org/">Rails API</a></li>
<li><a href="http://stdlib.rubyonrails.org/">Ruby standard library</a></li>
<li><a href="http://corelib.rubyonrails.org/">Ruby core</a></li>
<li><a href="http://guides.rubyonrails.org/">Rails Guides</a></li>
</ul>
</li>
</ul>
</div>
<div id="content">
<div id="header">
<h1>Welcome aboard</h1>
<h2>You&rsquo;re riding Ruby on Rails!</h2>
</div>
<div id="about">
<h3><a href="rails/info/properties" onclick="about(); return false">About your application&rsquo;s environment</a></h3>
<div id="about-content" style="display: none"></div>
</div>
<div id="getting-started">
<h1>Getting started</h1>
<h2>Here&rsquo;s how to get rolling:</h2>
<ol>
<li>
<h2>Use <code>rails generate</code> to create your models and controllers</h2>
<p>To see all available options, run it without parameters.</p>
</li>
<li>
<h2>Set up a default route and remove or rename this file</h2>
<p>Routes are set up in config/routes.rb.</p>
</li>
<li>
<h2>Create your database</h2>
<p>Run <code>rake db:migrate</code> to create your database. If you're not using SQLite (the default), edit <code>config/database.yml</code> with your username and password.</p>
</li>
</ol>
</div>
</div>
<div id="footer">&nbsp;</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,2 @@
// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults

View File

@@ -0,0 +1,965 @@
// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
// Contributors:
// Richard Livsey
// Rahul Bhargava
// Rob Wills
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.
if(typeof Effect == 'undefined')
throw("controls.js requires including script.aculo.us' effects.js library");
var Autocompleter = { };
Autocompleter.Base = Class.create({
baseInitialize: function(element, update, options) {
element = $(element);
this.element = element;
this.update = $(update);
this.hasFocus = false;
this.changed = false;
this.active = false;
this.index = 0;
this.entryCount = 0;
this.oldElementValue = this.element.value;
if(this.setOptions)
this.setOptions(options);
else
this.options = options || { };
this.options.paramName = this.options.paramName || this.element.name;
this.options.tokens = this.options.tokens || [];
this.options.frequency = this.options.frequency || 0.4;
this.options.minChars = this.options.minChars || 1;
this.options.onShow = this.options.onShow ||
function(element, update){
if(!update.style.position || update.style.position=='absolute') {
update.style.position = 'absolute';
Position.clone(element, update, {
setHeight: false,
offsetTop: element.offsetHeight
});
}
Effect.Appear(update,{duration:0.15});
};
this.options.onHide = this.options.onHide ||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
if(typeof(this.options.tokens) == 'string')
this.options.tokens = new Array(this.options.tokens);
// Force carriage returns as token delimiters anyway
if (!this.options.tokens.include('\n'))
this.options.tokens.push('\n');
this.observer = null;
this.element.setAttribute('autocomplete','off');
Element.hide(this.update);
Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
},
show: function() {
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
if(!this.iefix &&
(Prototype.Browser.IE) &&
(Element.getStyle(this.update, 'position')=='absolute')) {
new Insertion.After(this.update,
'<iframe id="' + this.update.id + '_iefix" '+
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
this.iefix = $(this.update.id+'_iefix');
}
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
},
fixIEOverlapping: function() {
Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
this.iefix.style.zIndex = 1;
this.update.style.zIndex = 2;
Element.show(this.iefix);
},
hide: function() {
this.stopIndicator();
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
if(this.iefix) Element.hide(this.iefix);
},
startIndicator: function() {
if(this.options.indicator) Element.show(this.options.indicator);
},
stopIndicator: function() {
if(this.options.indicator) Element.hide(this.options.indicator);
},
onKeyPress: function(event) {
if(this.active)
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
this.selectEntry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
Event.stop(event);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
this.markPrevious();
this.render();
Event.stop(event);
return;
case Event.KEY_DOWN:
this.markNext();
this.render();
Event.stop(event);
return;
}
else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
(Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
this.changed = true;
this.hasFocus = true;
if(this.observer) clearTimeout(this.observer);
this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
activate: function() {
this.changed = false;
this.hasFocus = true;
this.getUpdatedChoices();
},
onHover: function(event) {
var element = Event.findElement(event, 'LI');
if(this.index != element.autocompleteIndex)
{
this.index = element.autocompleteIndex;
this.render();
}
Event.stop(event);
},
onClick: function(event) {
var element = Event.findElement(event, 'LI');
this.index = element.autocompleteIndex;
this.selectEntry();
this.hide();
},
onBlur: function(event) {
// needed to make click events working
setTimeout(this.hide.bind(this), 250);
this.hasFocus = false;
this.active = false;
},
render: function() {
if(this.entryCount > 0) {
for (var i = 0; i < this.entryCount; i++)
this.index==i ?
Element.addClassName(this.getEntry(i),"selected") :
Element.removeClassName(this.getEntry(i),"selected");
if(this.hasFocus) {
this.show();
this.active = true;
}
} else {
this.active = false;
this.hide();
}
},
markPrevious: function() {
if(this.index > 0) this.index--;
else this.index = this.entryCount-1;
this.getEntry(this.index).scrollIntoView(true);
},
markNext: function() {
if(this.index < this.entryCount-1) this.index++;
else this.index = 0;
this.getEntry(this.index).scrollIntoView(false);
},
getEntry: function(index) {
return this.update.firstChild.childNodes[index];
},
getCurrentEntry: function() {
return this.getEntry(this.index);
},
selectEntry: function() {
this.active = false;
this.updateElement(this.getCurrentEntry());
},
updateElement: function(selectedElement) {
if (this.options.updateElement) {
this.options.updateElement(selectedElement);
return;
}
var value = '';
if (this.options.select) {
var nodes = $(selectedElement).select('.' + this.options.select) || [];
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
} else
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
var bounds = this.getTokenBounds();
if (bounds[0] != -1) {
var newValue = this.element.value.substr(0, bounds[0]);
var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
if (whitespace)
newValue += whitespace[0];
this.element.value = newValue + value + this.element.value.substr(bounds[1]);
} else {
this.element.value = value;
}
this.oldElementValue = this.element.value;
this.element.focus();
if (this.options.afterUpdateElement)
this.options.afterUpdateElement(this.element, selectedElement);
},
updateChoices: function(choices) {
if(!this.changed && this.hasFocus) {
this.update.innerHTML = choices;
Element.cleanWhitespace(this.update);
Element.cleanWhitespace(this.update.down());
if(this.update.firstChild && this.update.down().childNodes) {
this.entryCount =
this.update.down().childNodes.length;
for (var i = 0; i < this.entryCount; i++) {
var entry = this.getEntry(i);
entry.autocompleteIndex = i;
this.addObservers(entry);
}
} else {
this.entryCount = 0;
}
this.stopIndicator();
this.index = 0;
if(this.entryCount==1 && this.options.autoSelect) {
this.selectEntry();
this.hide();
} else {
this.render();
}
}
},
addObservers: function(element) {
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
},
onObserverEvent: function() {
this.changed = false;
this.tokenBounds = null;
if(this.getToken().length>=this.options.minChars) {
this.getUpdatedChoices();
} else {
this.active = false;
this.hide();
}
this.oldElementValue = this.element.value;
},
getToken: function() {
var bounds = this.getTokenBounds();
return this.element.value.substring(bounds[0], bounds[1]).strip();
},
getTokenBounds: function() {
if (null != this.tokenBounds) return this.tokenBounds;
var value = this.element.value;
if (value.strip().empty()) return [-1, 0];
var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
var offset = (diff == this.oldElementValue.length ? 1 : 0);
var prevTokenPos = -1, nextTokenPos = value.length;
var tp;
for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
if (tp > prevTokenPos) prevTokenPos = tp;
tp = value.indexOf(this.options.tokens[index], diff + offset);
if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
}
return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
}
});
Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
var boundary = Math.min(newS.length, oldS.length);
for (var index = 0; index < boundary; ++index)
if (newS[index] != oldS[index])
return index;
return boundary;
};
Ajax.Autocompleter = Class.create(Autocompleter.Base, {
initialize: function(element, update, url, options) {
this.baseInitialize(element, update, options);
this.options.asynchronous = true;
this.options.onComplete = this.onComplete.bind(this);
this.options.defaultParams = this.options.parameters || null;
this.url = url;
},
getUpdatedChoices: function() {
this.startIndicator();
var entry = encodeURIComponent(this.options.paramName) + '=' +
encodeURIComponent(this.getToken());
this.options.parameters = this.options.callback ?
this.options.callback(this.element, entry) : entry;
if(this.options.defaultParams)
this.options.parameters += '&' + this.options.defaultParams;
new Ajax.Request(this.url, this.options);
},
onComplete: function(request) {
this.updateChoices(request.responseText);
}
});
// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
// text only at the beginning of strings in the
// autocomplete array. Defaults to true, which will
// match text at the beginning of any *word* in the
// strings in the autocomplete array. If you want to
// search anywhere in the string, additionally set
// the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
// a partial match (unlike minChars, which defines
// how many characters are required to do any match
// at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
// Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.
Autocompleter.Local = Class.create(Autocompleter.Base, {
initialize: function(element, update, array, options) {
this.baseInitialize(element, update, options);
this.options.array = array;
},
getUpdatedChoices: function() {
this.updateChoices(this.options.selector(this));
},
setOptions: function(options) {
this.options = Object.extend({
choices: 10,
partialSearch: true,
partialChars: 2,
ignoreCase: true,
fullSearch: false,
selector: function(instance) {
var ret = []; // Beginning matches
var partial = []; // Inside matches
var entry = instance.getToken();
var count = 0;
for (var i = 0; i < instance.options.array.length &&
ret.length < instance.options.choices ; i++) {
var elem = instance.options.array[i];
var foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
while (foundPos != -1) {
if (foundPos == 0 && elem.length != entry.length) {
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
elem.substr(entry.length) + "</li>");
break;
} else if (entry.length >= instance.options.partialChars &&
instance.options.partialSearch && foundPos != -1) {
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
foundPos + entry.length) + "</li>");
break;
}
}
foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
elem.indexOf(entry, foundPos + 1);
}
}
if (partial.length)
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
return "<ul>" + ret.join('') + "</ul>";
}
}, options || { });
}
});
// AJAX in-place editor and collection editor
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
setTimeout(function() {
Field.activate(field);
}, 1);
};
Ajax.InPlaceEditor = Class.create({
initialize: function(element, url, options) {
this.url = url;
this.element = element = $(element);
this.prepareOptions();
this._controls = { };
arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
Object.extend(this.options, options || { });
if (!this.options.formId && this.element.id) {
this.options.formId = this.element.id + '-inplaceeditor';
if ($(this.options.formId))
this.options.formId = '';
}
if (this.options.externalControl)
this.options.externalControl = $(this.options.externalControl);
if (!this.options.externalControl)
this.options.externalControlOnly = false;
this._originalBackground = this.element.getStyle('background-color') || 'transparent';
this.element.title = this.options.clickToEditText;
this._boundCancelHandler = this.handleFormCancellation.bind(this);
this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
this._boundFailureHandler = this.handleAJAXFailure.bind(this);
this._boundSubmitHandler = this.handleFormSubmission.bind(this);
this._boundWrapperHandler = this.wrapUp.bind(this);
this.registerListeners();
},
checkForEscapeOrReturn: function(e) {
if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
if (Event.KEY_ESC == e.keyCode)
this.handleFormCancellation(e);
else if (Event.KEY_RETURN == e.keyCode)
this.handleFormSubmission(e);
},
createControl: function(mode, handler, extraClasses) {
var control = this.options[mode + 'Control'];
var text = this.options[mode + 'Text'];
if ('button' == control) {
var btn = document.createElement('input');
btn.type = 'submit';
btn.value = text;
btn.className = 'editor_' + mode + '_button';
if ('cancel' == mode)
btn.onclick = this._boundCancelHandler;
this._form.appendChild(btn);
this._controls[mode] = btn;
} else if ('link' == control) {
var link = document.createElement('a');
link.href = '#';
link.appendChild(document.createTextNode(text));
link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
link.className = 'editor_' + mode + '_link';
if (extraClasses)
link.className += ' ' + extraClasses;
this._form.appendChild(link);
this._controls[mode] = link;
}
},
createEditField: function() {
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
var fld;
if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
fld = document.createElement('input');
fld.type = 'text';
var size = this.options.size || this.options.cols || 0;
if (0 < size) fld.size = size;
} else {
fld = document.createElement('textarea');
fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
fld.cols = this.options.cols || 40;
}
fld.name = this.options.paramName;
fld.value = text; // No HTML breaks conversion anymore
fld.className = 'editor_field';
if (this.options.submitOnBlur)
fld.onblur = this._boundSubmitHandler;
this._controls.editor = fld;
if (this.options.loadTextURL)
this.loadExternalText();
this._form.appendChild(this._controls.editor);
},
createForm: function() {
var ipe = this;
function addText(mode, condition) {
var text = ipe.options['text' + mode + 'Controls'];
if (!text || condition === false) return;
ipe._form.appendChild(document.createTextNode(text));
};
this._form = $(document.createElement('form'));
this._form.id = this.options.formId;
this._form.addClassName(this.options.formClassName);
this._form.onsubmit = this._boundSubmitHandler;
this.createEditField();
if ('textarea' == this._controls.editor.tagName.toLowerCase())
this._form.appendChild(document.createElement('br'));
if (this.options.onFormCustomization)
this.options.onFormCustomization(this, this._form);
addText('Before', this.options.okControl || this.options.cancelControl);
this.createControl('ok', this._boundSubmitHandler);
addText('Between', this.options.okControl && this.options.cancelControl);
this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
addText('After', this.options.okControl || this.options.cancelControl);
},
destroy: function() {
if (this._oldInnerHTML)
this.element.innerHTML = this._oldInnerHTML;
this.leaveEditMode();
this.unregisterListeners();
},
enterEditMode: function(e) {
if (this._saving || this._editing) return;
this._editing = true;
this.triggerCallback('onEnterEditMode');
if (this.options.externalControl)
this.options.externalControl.hide();
this.element.hide();
this.createForm();
this.element.parentNode.insertBefore(this._form, this.element);
if (!this.options.loadTextURL)
this.postProcessEditField();
if (e) Event.stop(e);
},
enterHover: function(e) {
if (this.options.hoverClassName)
this.element.addClassName(this.options.hoverClassName);
if (this._saving) return;
this.triggerCallback('onEnterHover');
},
getText: function() {
return this.element.innerHTML.unescapeHTML();
},
handleAJAXFailure: function(transport) {
this.triggerCallback('onFailure', transport);
if (this._oldInnerHTML) {
this.element.innerHTML = this._oldInnerHTML;
this._oldInnerHTML = null;
}
},
handleFormCancellation: function(e) {
this.wrapUp();
if (e) Event.stop(e);
},
handleFormSubmission: function(e) {
var form = this._form;
var value = $F(this._controls.editor);
this.prepareSubmission();
var params = this.options.callback(form, value) || '';
if (Object.isString(params))
params = params.toQueryParams();
params.editorId = this.element.id;
if (this.options.htmlResponse) {
var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
Object.extend(options, {
parameters: params,
onComplete: this._boundWrapperHandler,
onFailure: this._boundFailureHandler
});
new Ajax.Updater({ success: this.element }, this.url, options);
} else {
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: params,
onComplete: this._boundWrapperHandler,
onFailure: this._boundFailureHandler
});
new Ajax.Request(this.url, options);
}
if (e) Event.stop(e);
},
leaveEditMode: function() {
this.element.removeClassName(this.options.savingClassName);
this.removeForm();
this.leaveHover();
this.element.style.backgroundColor = this._originalBackground;
this.element.show();
if (this.options.externalControl)
this.options.externalControl.show();
this._saving = false;
this._editing = false;
this._oldInnerHTML = null;
this.triggerCallback('onLeaveEditMode');
},
leaveHover: function(e) {
if (this.options.hoverClassName)
this.element.removeClassName(this.options.hoverClassName);
if (this._saving) return;
this.triggerCallback('onLeaveHover');
},
loadExternalText: function() {
this._form.addClassName(this.options.loadingClassName);
this._controls.editor.disabled = true;
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
this._form.removeClassName(this.options.loadingClassName);
var text = transport.responseText;
if (this.options.stripLoadedTextTags)
text = text.stripTags();
this._controls.editor.value = text;
this._controls.editor.disabled = false;
this.postProcessEditField();
}.bind(this),
onFailure: this._boundFailureHandler
});
new Ajax.Request(this.options.loadTextURL, options);
},
postProcessEditField: function() {
var fpc = this.options.fieldPostCreation;
if (fpc)
$(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
},
prepareOptions: function() {
this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
[this._extraDefaultOptions].flatten().compact().each(function(defs) {
Object.extend(this.options, defs);
}.bind(this));
},
prepareSubmission: function() {
this._saving = true;
this.removeForm();
this.leaveHover();
this.showSaving();
},
registerListeners: function() {
this._listeners = { };
var listener;
$H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
listener = this[pair.value].bind(this);
this._listeners[pair.key] = listener;
if (!this.options.externalControlOnly)
this.element.observe(pair.key, listener);
if (this.options.externalControl)
this.options.externalControl.observe(pair.key, listener);
}.bind(this));
},
removeForm: function() {
if (!this._form) return;
this._form.remove();
this._form = null;
this._controls = { };
},
showSaving: function() {
this._oldInnerHTML = this.element.innerHTML;
this.element.innerHTML = this.options.savingText;
this.element.addClassName(this.options.savingClassName);
this.element.style.backgroundColor = this._originalBackground;
this.element.show();
},
triggerCallback: function(cbName, arg) {
if ('function' == typeof this.options[cbName]) {
this.options[cbName](this, arg);
}
},
unregisterListeners: function() {
$H(this._listeners).each(function(pair) {
if (!this.options.externalControlOnly)
this.element.stopObserving(pair.key, pair.value);
if (this.options.externalControl)
this.options.externalControl.stopObserving(pair.key, pair.value);
}.bind(this));
},
wrapUp: function(transport) {
this.leaveEditMode();
// Can't use triggerCallback due to backward compatibility: requires
// binding + direct element
this._boundComplete(transport, this.element);
}
});
Object.extend(Ajax.InPlaceEditor.prototype, {
dispose: Ajax.InPlaceEditor.prototype.destroy
});
Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
initialize: function($super, element, url, options) {
this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
$super(element, url, options);
},
createEditField: function() {
var list = document.createElement('select');
list.name = this.options.paramName;
list.size = 1;
this._controls.editor = list;
this._collection = this.options.collection || [];
if (this.options.loadCollectionURL)
this.loadCollection();
else
this.checkForExternalText();
this._form.appendChild(this._controls.editor);
},
loadCollection: function() {
this._form.addClassName(this.options.loadingClassName);
this.showLoadingText(this.options.loadingCollectionText);
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
var js = transport.responseText.strip();
if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
throw('Server returned an invalid collection representation.');
this._collection = eval(js);
this.checkForExternalText();
}.bind(this),
onFailure: this.onFailure
});
new Ajax.Request(this.options.loadCollectionURL, options);
},
showLoadingText: function(text) {
this._controls.editor.disabled = true;
var tempOption = this._controls.editor.firstChild;
if (!tempOption) {
tempOption = document.createElement('option');
tempOption.value = '';
this._controls.editor.appendChild(tempOption);
tempOption.selected = true;
}
tempOption.update((text || '').stripScripts().stripTags());
},
checkForExternalText: function() {
this._text = this.getText();
if (this.options.loadTextURL)
this.loadExternalText();
else
this.buildOptionList();
},
loadExternalText: function() {
this.showLoadingText(this.options.loadingText);
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
this._text = transport.responseText.strip();
this.buildOptionList();
}.bind(this),
onFailure: this.onFailure
});
new Ajax.Request(this.options.loadTextURL, options);
},
buildOptionList: function() {
this._form.removeClassName(this.options.loadingClassName);
this._collection = this._collection.map(function(entry) {
return 2 === entry.length ? entry : [entry, entry].flatten();
});
var marker = ('value' in this.options) ? this.options.value : this._text;
var textFound = this._collection.any(function(entry) {
return entry[0] == marker;
}.bind(this));
this._controls.editor.update('');
var option;
this._collection.each(function(entry, index) {
option = document.createElement('option');
option.value = entry[0];
option.selected = textFound ? entry[0] == marker : 0 == index;
option.appendChild(document.createTextNode(entry[1]));
this._controls.editor.appendChild(option);
}.bind(this));
this._controls.editor.disabled = false;
Field.scrollFreeActivate(this._controls.editor);
}
});
//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
//**** This only exists for a while, in order to let ****
//**** users adapt to the new API. Read up on the new ****
//**** API and convert your code to it ASAP! ****
Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
if (!options) return;
function fallback(name, expr) {
if (name in options || expr === undefined) return;
options[name] = expr;
};
fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
options.cancelLink == options.cancelButton == false ? false : undefined)));
fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
options.okLink == options.okButton == false ? false : undefined)));
fallback('highlightColor', options.highlightcolor);
fallback('highlightEndColor', options.highlightendcolor);
};
Object.extend(Ajax.InPlaceEditor, {
DefaultOptions: {
ajaxOptions: { },
autoRows: 3, // Use when multi-line w/ rows == 1
cancelControl: 'link', // 'link'|'button'|false
cancelText: 'cancel',
clickToEditText: 'Click to edit',
externalControl: null, // id|elt
externalControlOnly: false,
fieldPostCreation: 'activate', // 'activate'|'focus'|false
formClassName: 'inplaceeditor-form',
formId: null, // id|elt
highlightColor: '#ffff99',
highlightEndColor: '#ffffff',
hoverClassName: '',
htmlResponse: true,
loadingClassName: 'inplaceeditor-loading',
loadingText: 'Loading...',
okControl: 'button', // 'link'|'button'|false
okText: 'ok',
paramName: 'value',
rows: 1, // If 1 and multi-line, uses autoRows
savingClassName: 'inplaceeditor-saving',
savingText: 'Saving...',
size: 0,
stripLoadedTextTags: false,
submitOnBlur: false,
textAfterControls: '',
textBeforeControls: '',
textBetweenControls: ''
},
DefaultCallbacks: {
callback: function(form) {
return Form.serialize(form);
},
onComplete: function(transport, element) {
// For backward compatibility, this one is bound to the IPE, and passes
// the element directly. It was too often customized, so we don't break it.
new Effect.Highlight(element, {
startcolor: this.options.highlightColor, keepBackgroundImage: true });
},
onEnterEditMode: null,
onEnterHover: function(ipe) {
ipe.element.style.backgroundColor = ipe.options.highlightColor;
if (ipe._effect)
ipe._effect.cancel();
},
onFailure: function(transport, ipe) {
alert('Error communication with the server: ' + transport.responseText.stripTags());
},
onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
onLeaveEditMode: null,
onLeaveHover: function(ipe) {
ipe._effect = new Effect.Highlight(ipe.element, {
startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
restorecolor: ipe._originalBackground, keepBackgroundImage: true
});
}
},
Listeners: {
click: 'enterEditMode',
keydown: 'checkForEscapeOrReturn',
mouseover: 'enterHover',
mouseout: 'leaveHover'
}
});
Ajax.InPlaceCollectionEditor.DefaultOptions = {
loadingCollectionText: 'Loading options...'
};
// Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input
// Ideal for live-search fields
Form.Element.DelayedObserver = Class.create({
initialize: function(element, delay, callback) {
this.delay = delay || 0.5;
this.element = $(element);
this.callback = callback;
this.timer = null;
this.lastValue = $F(this.element);
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
},
delayedListener: function(event) {
if(this.lastValue == $F(this.element)) return;
if(this.timer) clearTimeout(this.timer);
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
this.lastValue = $F(this.element);
},
onTimerEvent: function() {
this.timer = null;
this.callback(this.element, $F(this.element));
}
});

View File

@@ -0,0 +1,974 @@
// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
if(Object.isUndefined(Effect))
throw("dragdrop.js requires including script.aculo.us' effects.js library");
var Droppables = {
drops: [],
remove: function(element) {
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
},
add: function(element) {
element = $(element);
var options = Object.extend({
greedy: true,
hoverclass: null,
tree: false
}, arguments[1] || { });
// cache containers
if(options.containment) {
options._containers = [];
var containment = options.containment;
if(Object.isArray(containment)) {
containment.each( function(c) { options._containers.push($(c)) });
} else {
options._containers.push($(containment));
}
}
if(options.accept) options.accept = [options.accept].flatten();
Element.makePositioned(element); // fix IE
options.element = element;
this.drops.push(options);
},
findDeepestChild: function(drops) {
deepest = drops[0];
for (i = 1; i < drops.length; ++i)
if (Element.isParent(drops[i].element, deepest.element))
deepest = drops[i];
return deepest;
},
isContained: function(element, drop) {
var containmentNode;
if(drop.tree) {
containmentNode = element.treeNode;
} else {
containmentNode = element.parentNode;
}
return drop._containers.detect(function(c) { return containmentNode == c });
},
isAffected: function(point, element, drop) {
return (
(drop.element!=element) &&
((!drop._containers) ||
this.isContained(element, drop)) &&
((!drop.accept) ||
(Element.classNames(element).detect(
function(v) { return drop.accept.include(v) } ) )) &&
Position.within(drop.element, point[0], point[1]) );
},
deactivate: function(drop) {
if(drop.hoverclass)
Element.removeClassName(drop.element, drop.hoverclass);
this.last_active = null;
},
activate: function(drop) {
if(drop.hoverclass)
Element.addClassName(drop.element, drop.hoverclass);
this.last_active = drop;
},
show: function(point, element) {
if(!this.drops.length) return;
var drop, affected = [];
this.drops.each( function(drop) {
if(Droppables.isAffected(point, element, drop))
affected.push(drop);
});
if(affected.length>0)
drop = Droppables.findDeepestChild(affected);
if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
if (drop) {
Position.within(drop.element, point[0], point[1]);
if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if (drop != this.last_active) Droppables.activate(drop);
}
},
fire: function(event, element) {
if(!this.last_active) return;
Position.prepare();
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
if (this.last_active.onDrop) {
this.last_active.onDrop(element, this.last_active.element, event);
return true;
}
},
reset: function() {
if(this.last_active)
this.deactivate(this.last_active);
}
};
var Draggables = {
drags: [],
observers: [],
register: function(draggable) {
if(this.drags.length == 0) {
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
Event.observe(document, "keypress", this.eventKeypress);
}
this.drags.push(draggable);
},
unregister: function(draggable) {
this.drags = this.drags.reject(function(d) { return d==draggable });
if(this.drags.length == 0) {
Event.stopObserving(document, "mouseup", this.eventMouseUp);
Event.stopObserving(document, "mousemove", this.eventMouseMove);
Event.stopObserving(document, "keypress", this.eventKeypress);
}
},
activate: function(draggable) {
if(draggable.options.delay) {
this._timeout = setTimeout(function() {
Draggables._timeout = null;
window.focus();
Draggables.activeDraggable = draggable;
}.bind(this), draggable.options.delay);
} else {
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
this.activeDraggable = draggable;
}
},
deactivate: function() {
this.activeDraggable = null;
},
updateDrag: function(event) {
if(!this.activeDraggable) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
// Mozilla-based browsers fire successive mousemove events with
// the same coordinates, prevent needless redrawing (moz bug?)
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
this._lastPointer = pointer;
this.activeDraggable.updateDrag(event, pointer);
},
endDrag: function(event) {
if(this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if(!this.activeDraggable) return;
this._lastPointer = null;
this.activeDraggable.endDrag(event);
this.activeDraggable = null;
},
keyPress: function(event) {
if(this.activeDraggable)
this.activeDraggable.keyPress(event);
},
addObserver: function(observer) {
this.observers.push(observer);
this._cacheObserverCallbacks();
},
removeObserver: function(element) { // element instead of observer fixes mem leaks
this.observers = this.observers.reject( function(o) { return o.element==element });
this._cacheObserverCallbacks();
},
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
if(this[eventName+'Count'] > 0)
this.observers.each( function(o) {
if(o[eventName]) o[eventName](eventName, draggable, event);
});
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
},
_cacheObserverCallbacks: function() {
['onStart','onEnd','onDrag'].each( function(eventName) {
Draggables[eventName+'Count'] = Draggables.observers.select(
function(o) { return o[eventName]; }
).length;
});
}
};
/*--------------------------------------------------------------------------*/
var Draggable = Class.create({
initialize: function(element) {
var defaults = {
handle: false,
reverteffect: function(element, top_offset, left_offset) {
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
queue: {scope:'_draggable', position:'end'}
});
},
endeffect: function(element) {
var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
queue: {scope:'_draggable', position:'end'},
afterFinish: function(){
Draggable._dragging[element] = false
}
});
},
zindex: 1000,
revert: false,
quiet: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
delay: 0
};
if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
Object.extend(defaults, {
starteffect: function(element) {
element._opacity = Element.getOpacity(element);
Draggable._dragging[element] = true;
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
}
});
var options = Object.extend(defaults, arguments[1] || { });
this.element = $(element);
if(options.handle && Object.isString(options.handle))
this.handle = this.element.down('.'+options.handle, 0);
if(!this.handle) this.handle = $(options.handle);
if(!this.handle) this.handle = this.element;
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
options.scroll = $(options.scroll);
this._isScrollChild = Element.childOf(this.element, options.scroll);
}
Element.makePositioned(this.element); // fix IE
this.options = options;
this.dragging = false;
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
Event.observe(this.handle, "mousedown", this.eventMouseDown);
Draggables.register(this);
},
destroy: function() {
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
Draggables.unregister(this);
},
currentDelta: function() {
return([
parseInt(Element.getStyle(this.element,'left') || '0'),
parseInt(Element.getStyle(this.element,'top') || '0')]);
},
initDrag: function(event) {
if(!Object.isUndefined(Draggable._dragging[this.element]) &&
Draggable._dragging[this.element]) return;
if(Event.isLeftClick(event)) {
// abort on form elements, fixes a Firefox issue
var src = Event.element(event);
if((tag_name = src.tagName.toUpperCase()) && (
tag_name=='INPUT' ||
tag_name=='SELECT' ||
tag_name=='OPTION' ||
tag_name=='BUTTON' ||
tag_name=='TEXTAREA')) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var pos = this.element.cumulativeOffset();
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
Draggables.activate(this);
Event.stop(event);
}
},
startDrag: function(event) {
this.dragging = true;
if(!this.delta)
this.delta = this.currentDelta();
if(this.options.zindex) {
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
this.element.style.zIndex = this.options.zindex;
}
if(this.options.ghosting) {
this._clone = this.element.cloneNode(true);
this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
if (!this._originallyAbsolute)
Position.absolutize(this.element);
this.element.parentNode.insertBefore(this._clone, this.element);
}
if(this.options.scroll) {
if (this.options.scroll == window) {
var where = this._getWindowScroll(this.options.scroll);
this.originalScrollLeft = where.left;
this.originalScrollTop = where.top;
} else {
this.originalScrollLeft = this.options.scroll.scrollLeft;
this.originalScrollTop = this.options.scroll.scrollTop;
}
}
Draggables.notify('onStart', this, event);
if(this.options.starteffect) this.options.starteffect(this.element);
},
updateDrag: function(event, pointer) {
if(!this.dragging) this.startDrag(event);
if(!this.options.quiet){
Position.prepare();
Droppables.show(pointer, this.element);
}
Draggables.notify('onDrag', this, event);
this.draw(pointer);
if(this.options.change) this.options.change(this);
if(this.options.scroll) {
this.stopScrolling();
var p;
if (this.options.scroll == window) {
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
} else {
p = Position.page(this.options.scroll);
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
p[1] += this.options.scroll.scrollTop + Position.deltaY;
p.push(p[0]+this.options.scroll.offsetWidth);
p.push(p[1]+this.options.scroll.offsetHeight);
}
var speed = [0,0];
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
this.startScrolling(speed);
}
// fix AppleWebKit rendering
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
Event.stop(event);
},
finishDrag: function(event, success) {
this.dragging = false;
if(this.options.quiet){
Position.prepare();
var pointer = [Event.pointerX(event), Event.pointerY(event)];
Droppables.show(pointer, this.element);
}
if(this.options.ghosting) {
if (!this._originallyAbsolute)
Position.relativize(this.element);
delete this._originallyAbsolute;
Element.remove(this._clone);
this._clone = null;
}
var dropped = false;
if(success) {
dropped = Droppables.fire(event, this.element);
if (!dropped) dropped = false;
}
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
Draggables.notify('onEnd', this, event);
var revert = this.options.revert;
if(revert && Object.isFunction(revert)) revert = revert(this.element);
var d = this.currentDelta();
if(revert && this.options.reverteffect) {
if (dropped == 0 || revert != 'failure')
this.options.reverteffect(this.element,
d[1]-this.delta[1], d[0]-this.delta[0]);
} else {
this.delta = d;
}
if(this.options.zindex)
this.element.style.zIndex = this.originalZ;
if(this.options.endeffect)
this.options.endeffect(this.element);
Draggables.deactivate(this);
Droppables.reset();
},
keyPress: function(event) {
if(event.keyCode!=Event.KEY_ESC) return;
this.finishDrag(event, false);
Event.stop(event);
},
endDrag: function(event) {
if(!this.dragging) return;
this.stopScrolling();
this.finishDrag(event, true);
Event.stop(event);
},
draw: function(point) {
var pos = this.element.cumulativeOffset();
if(this.options.ghosting) {
var r = Position.realOffset(this.element);
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
}
var d = this.currentDelta();
pos[0] -= d[0]; pos[1] -= d[1];
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
}
var p = [0,1].map(function(i){
return (point[i]-pos[i]-this.offset[i])
}.bind(this));
if(this.options.snap) {
if(Object.isFunction(this.options.snap)) {
p = this.options.snap(p[0],p[1],this);
} else {
if(Object.isArray(this.options.snap)) {
p = p.map( function(v, i) {
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
} else {
p = p.map( function(v) {
return (v/this.options.snap).round()*this.options.snap }.bind(this));
}
}}
var style = this.element.style;
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
style.left = p[0] + "px";
if((!this.options.constraint) || (this.options.constraint=='vertical'))
style.top = p[1] + "px";
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
},
stopScrolling: function() {
if(this.scrollInterval) {
clearInterval(this.scrollInterval);
this.scrollInterval = null;
Draggables._lastScrollPointer = null;
}
},
startScrolling: function(speed) {
if(!(speed[0] || speed[1])) return;
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
this.lastScrolled = new Date();
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
},
scroll: function() {
var current = new Date();
var delta = current - this.lastScrolled;
this.lastScrolled = current;
if(this.options.scroll == window) {
with (this._getWindowScroll(this.options.scroll)) {
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
var d = delta / 1000;
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
}
}
} else {
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
}
Position.prepare();
Droppables.show(Draggables._lastPointer, this.element);
Draggables.notify('onDrag', this);
if (this._isScrollChild) {
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
if (Draggables._lastScrollPointer[0] < 0)
Draggables._lastScrollPointer[0] = 0;
if (Draggables._lastScrollPointer[1] < 0)
Draggables._lastScrollPointer[1] = 0;
this.draw(Draggables._lastScrollPointer);
}
if(this.options.change) this.options.change(this);
},
_getWindowScroll: function(w) {
var T, L, W, H;
with (w.document) {
if (w.document.documentElement && documentElement.scrollTop) {
T = documentElement.scrollTop;
L = documentElement.scrollLeft;
} else if (w.document.body) {
T = body.scrollTop;
L = body.scrollLeft;
}
if (w.innerWidth) {
W = w.innerWidth;
H = w.innerHeight;
} else if (w.document.documentElement && documentElement.clientWidth) {
W = documentElement.clientWidth;
H = documentElement.clientHeight;
} else {
W = body.offsetWidth;
H = body.offsetHeight;
}
}
return { top: T, left: L, width: W, height: H };
}
});
Draggable._dragging = { };
/*--------------------------------------------------------------------------*/
var SortableObserver = Class.create({
initialize: function(element, observer) {
this.element = $(element);
this.observer = observer;
this.lastValue = Sortable.serialize(this.element);
},
onStart: function() {
this.lastValue = Sortable.serialize(this.element);
},
onEnd: function() {
Sortable.unmark();
if(this.lastValue != Sortable.serialize(this.element))
this.observer(this.element)
}
});
var Sortable = {
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
sortables: { },
_findRootElement: function(element) {
while (element.tagName.toUpperCase() != "BODY") {
if(element.id && Sortable.sortables[element.id]) return element;
element = element.parentNode;
}
},
options: function(element) {
element = Sortable._findRootElement($(element));
if(!element) return;
return Sortable.sortables[element.id];
},
destroy: function(element){
element = $(element);
var s = Sortable.sortables[element.id];
if(s) {
Draggables.removeObserver(s.element);
s.droppables.each(function(d){ Droppables.remove(d) });
s.draggables.invoke('destroy');
delete Sortable.sortables[s.element.id];
}
},
create: function(element) {
element = $(element);
var options = Object.extend({
element: element,
tag: 'li', // assumes li children, override with tag: 'tagname'
dropOnEmpty: false,
tree: false,
treeTag: 'ul',
overlap: 'vertical', // one of 'vertical', 'horizontal'
constraint: 'vertical', // one of 'vertical', 'horizontal', false
containment: element, // also takes array of elements (or id's); or false
handle: false, // or a CSS class
only: false,
delay: 0,
hoverclass: null,
ghosting: false,
quiet: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
format: this.SERIALIZE_RULE,
// these take arrays of elements or ids and can be
// used for better initialization performance
elements: false,
handles: false,
onChange: Prototype.emptyFunction,
onUpdate: Prototype.emptyFunction
}, arguments[1] || { });
// clear any old sortable with same element
this.destroy(element);
// build options for the draggables
var options_for_draggable = {
revert: true,
quiet: options.quiet,
scroll: options.scroll,
scrollSpeed: options.scrollSpeed,
scrollSensitivity: options.scrollSensitivity,
delay: options.delay,
ghosting: options.ghosting,
constraint: options.constraint,
handle: options.handle };
if(options.starteffect)
options_for_draggable.starteffect = options.starteffect;
if(options.reverteffect)
options_for_draggable.reverteffect = options.reverteffect;
else
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
element.style.top = 0;
element.style.left = 0;
};
if(options.endeffect)
options_for_draggable.endeffect = options.endeffect;
if(options.zindex)
options_for_draggable.zindex = options.zindex;
// build options for the droppables
var options_for_droppable = {
overlap: options.overlap,
containment: options.containment,
tree: options.tree,
hoverclass: options.hoverclass,
onHover: Sortable.onHover
};
var options_for_tree = {
onHover: Sortable.onEmptyHover,
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass
};
// fix for gecko engine
Element.cleanWhitespace(element);
options.draggables = [];
options.droppables = [];
// drop on empty handling
if(options.dropOnEmpty || options.tree) {
Droppables.add(element, options_for_tree);
options.droppables.push(element);
}
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
var handle = options.handles ? $(options.handles[i]) :
(options.handle ? $(e).select('.' + options.handle)[0] : e);
options.draggables.push(
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
Droppables.add(e, options_for_droppable);
if(options.tree) e.treeNode = element;
options.droppables.push(e);
});
if(options.tree) {
(Sortable.findTreeElements(element, options) || []).each( function(e) {
Droppables.add(e, options_for_tree);
e.treeNode = element;
options.droppables.push(e);
});
}
// keep reference
this.sortables[element.identify()] = options;
// for onupdate
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
},
// return all suitable-for-sortable elements in a guaranteed order
findElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.tag);
},
findTreeElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.treeTag);
},
onHover: function(element, dropon, overlap) {
if(Element.isParent(dropon, element)) return;
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
return;
} else if(overlap>0.5) {
Sortable.mark(dropon, 'before');
if(dropon.previousSibling != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, dropon);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
} else {
Sortable.mark(dropon, 'after');
var nextElement = dropon.nextSibling || null;
if(nextElement != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, nextElement);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
}
},
onEmptyHover: function(element, dropon, overlap) {
var oldParentNode = element.parentNode;
var droponOptions = Sortable.options(dropon);
if(!Element.isParent(dropon, element)) {
var index;
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
var child = null;
if(children) {
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
for (index = 0; index < children.length; index += 1) {
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
offset -= Element.offsetSize (children[index], droponOptions.overlap);
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
child = index + 1 < children.length ? children[index + 1] : null;
break;
} else {
child = children[index];
break;
}
}
}
dropon.insertBefore(element, child);
Sortable.options(oldParentNode).onChange(element);
droponOptions.onChange(element);
}
},
unmark: function() {
if(Sortable._marker) Sortable._marker.hide();
},
mark: function(dropon, position) {
// mark on ghosting only
var sortable = Sortable.options(dropon.parentNode);
if(sortable && !sortable.ghosting) return;
if(!Sortable._marker) {
Sortable._marker =
($('dropmarker') || Element.extend(document.createElement('DIV'))).
hide().addClassName('dropmarker').setStyle({position:'absolute'});
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
}
var offsets = dropon.cumulativeOffset();
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
if(position=='after')
if(sortable.overlap == 'horizontal')
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
else
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
Sortable._marker.show();
},
_tree: function(element, options, parent) {
var children = Sortable.findElements(element, options) || [];
for (var i = 0; i < children.length; ++i) {
var match = children[i].id.match(options.format);
if (!match) continue;
var child = {
id: encodeURIComponent(match ? match[1] : null),
element: element,
parent: parent,
children: [],
position: parent.children.length,
container: $(children[i]).down(options.treeTag)
};
/* Get the element containing the children and recurse over it */
if (child.container)
this._tree(child.container, options, child);
parent.children.push (child);
}
return parent;
},
tree: function(element) {
element = $(element);
var sortableOptions = this.options(element);
var options = Object.extend({
tag: sortableOptions.tag,
treeTag: sortableOptions.treeTag,
only: sortableOptions.only,
name: element.id,
format: sortableOptions.format
}, arguments[1] || { });
var root = {
id: null,
parent: null,
children: [],
container: element,
position: 0
};
return Sortable._tree(element, options, root);
},
/* Construct a [i] index for a particular node */
_constructIndex: function(node) {
var index = '';
do {
if (node.id) index = '[' + node.position + ']' + index;
} while ((node = node.parent) != null);
return index;
},
sequence: function(element) {
element = $(element);
var options = Object.extend(this.options(element), arguments[1] || { });
return $(this.findElements(element, options) || []).map( function(item) {
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
});
},
setSequence: function(element, new_sequence) {
element = $(element);
var options = Object.extend(this.options(element), arguments[2] || { });
var nodeMap = { };
this.findElements(element, options).each( function(n) {
if (n.id.match(options.format))
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
n.parentNode.removeChild(n);
});
new_sequence.each(function(ident) {
var n = nodeMap[ident];
if (n) {
n[1].appendChild(n[0]);
delete nodeMap[ident];
}
});
},
serialize: function(element) {
element = $(element);
var options = Object.extend(Sortable.options(element), arguments[1] || { });
var name = encodeURIComponent(
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
if (options.tree) {
return Sortable.tree(element, arguments[1]).children.map( function (item) {
return [name + Sortable._constructIndex(item) + "[id]=" +
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
}).flatten().join('&');
} else {
return Sortable.sequence(element, arguments[1]).map( function(item) {
return name + "[]=" + encodeURIComponent(item);
}).join('&');
}
}
};
// Returns true if child is contained within element
Element.isParent = function(child, element) {
if (!child.parentNode || child == element) return false;
if (child.parentNode == element) return true;
return Element.isParent(child.parentNode, element);
};
Element.findChildren = function(element, only, recursive, tagName) {
if(!element.hasChildNodes()) return null;
tagName = tagName.toUpperCase();
if(only) only = [only].flatten();
var elements = [];
$A(element.childNodes).each( function(e) {
if(e.tagName && e.tagName.toUpperCase()==tagName &&
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
elements.push(e);
if(recursive) {
var grandchildren = Element.findChildren(e, only, recursive, tagName);
if(grandchildren) elements.push(grandchildren);
}
});
return (elements.length>0 ? elements.flatten() : []);
};
Element.offsetSize = function (element, type) {
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,175 @@
(function() {
// Technique from Juriy Zaytsev
// http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
function isEventSupported(eventName) {
var el = document.createElement('div');
eventName = 'on' + eventName;
var isSupported = (eventName in el);
if (!isSupported) {
el.setAttribute(eventName, 'return;');
isSupported = typeof el[eventName] == 'function';
}
el = null;
return isSupported;
}
function isForm(element) {
return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
}
function isInput(element) {
if (Object.isElement(element)) {
var name = element.nodeName.toUpperCase()
return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
}
else return false
}
var submitBubbles = isEventSupported('submit'),
changeBubbles = isEventSupported('change')
if (!submitBubbles || !changeBubbles) {
// augment the Event.Handler class to observe custom events when needed
Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
function(init, element, eventName, selector, callback) {
init(element, eventName, selector, callback)
// is the handler being attached to an element that doesn't support this event?
if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
(!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
// "submit" => "emulated:submit"
this.eventName = 'emulated:' + this.eventName
}
}
)
}
if (!submitBubbles) {
// discover forms on the page by observing focus events which always bubble
document.on('focusin', 'form', function(focusEvent, form) {
// special handler for the real "submit" event (one-time operation)
if (!form.retrieve('emulated:submit')) {
form.on('submit', function(submitEvent) {
var emulated = form.fire('emulated:submit', submitEvent, true)
// if custom event received preventDefault, cancel the real one too
if (emulated.returnValue === false) submitEvent.preventDefault()
})
form.store('emulated:submit', true)
}
})
}
if (!changeBubbles) {
// discover form inputs on the page
document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
// special handler for real "change" events
if (!input.retrieve('emulated:change')) {
input.on('change', function(changeEvent) {
input.fire('emulated:change', changeEvent, true)
})
input.store('emulated:change', true)
}
})
}
function handleRemote(element) {
var method, url, params;
var event = element.fire("ajax:before");
if (event.stopped) return false;
if (element.tagName.toLowerCase() === 'form') {
method = element.readAttribute('method') || 'post';
url = element.readAttribute('action');
params = element.serialize();
} else {
method = element.readAttribute('data-method') || 'get';
url = element.readAttribute('href');
params = {};
}
new Ajax.Request(url, {
method: method,
parameters: params,
evalScripts: true,
onComplete: function(request) { element.fire("ajax:complete", request); },
onSuccess: function(request) { element.fire("ajax:success", request); },
onFailure: function(request) { element.fire("ajax:failure", request); }
});
element.fire("ajax:after");
}
function handleMethod(element) {
var method = element.readAttribute('data-method'),
url = element.readAttribute('href'),
csrf_param = $$('meta[name=csrf-param]')[0],
csrf_token = $$('meta[name=csrf-token]')[0];
var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
element.parentNode.insert(form);
if (method !== 'post') {
var field = new Element('input', { type: 'hidden', name: '_method', value: method });
form.insert(field);
}
if (csrf_param) {
var param = csrf_param.readAttribute('content'),
token = csrf_token.readAttribute('content'),
field = new Element('input', { type: 'hidden', name: param, value: token });
form.insert(field);
}
form.submit();
}
document.on("click", "*[data-confirm]", function(event, element) {
var message = element.readAttribute('data-confirm');
if (!confirm(message)) event.stop();
});
document.on("click", "a[data-remote]", function(event, element) {
if (event.stopped) return;
handleRemote(element);
event.stop();
});
document.on("click", "a[data-method]", function(event, element) {
if (event.stopped) return;
handleMethod(element);
event.stop();
});
document.on("submit", function(event) {
var element = event.findElement(),
message = element.readAttribute('data-confirm');
if (message && !confirm(message)) {
event.stop();
return false;
}
var inputs = element.select("input[type=submit][data-disable-with]");
inputs.each(function(input) {
input.disabled = true;
input.writeAttribute('data-original-value', input.value);
input.value = input.readAttribute('data-disable-with');
});
var element = event.findElement("form[data-remote]");
if (element) {
handleRemote(element);
event.stop();
}
});
document.on("ajax:after", "form", function(event, element) {
var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
inputs.each(function(input) {
input.value = input.readAttribute('data-original-value');
input.removeAttribute('data-original-value');
input.disabled = false;
});
});
})();

View File

@@ -0,0 +1,5 @@
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-Agent: *
# Disallow: /

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
APP_PATH = File.expand_path('../../config/application', __FILE__)
require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands'

View File

@@ -0,0 +1,11 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
# This model initially had no columns defined. If you add columns to the
# model remove the '{}' from the fixture names and add the columns immediately
# below each fixture, per the syntax in the comments below
#
one: {}
# column: value
#
two: {}
# column: value

View File

@@ -0,0 +1,8 @@
require 'test_helper'
class LoginsControllerControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

View File

@@ -0,0 +1,9 @@
require 'test_helper'
require 'rails/performance_test_help'
# Profiling results for each test method are written to tmp/performance.
class BrowsingTest < ActionDispatch::PerformanceTest
def test_homepage
get '/'
end
end

View File

@@ -0,0 +1,13 @@
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
fixtures :all
# Add more helper methods to be used by all tests here...
end

View File

@@ -0,0 +1,4 @@
require 'test_helper'
class LoginsControllerHelperTest < ActionView::TestCase
end

View File

@@ -0,0 +1,8 @@
require 'test_helper'
class UserTest < ActiveSupport::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

View File

@@ -0,0 +1,22 @@
Copyright (c) 2008 James Golick
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.

View File

@@ -0,0 +1,330 @@
= Resource Controller
resource_controller makes RESTful controllers easier, more maintainable, and super readable. With the RESTful controller pattern hidden away, you can focus on what makes your controller special.
== Get It
Install it as a plugin:
script/plugin install git://github.com/giraffesoft/resource_controller.git
Or grab the source
git clone git://github.com/giraffesoft/resource_controller.git
= Usage
Creating a basic RESTful controller is as easy as...
class PostsController < ResourceController::Base
end
...or if you prefer, you can use the method-call syntax. If you need to inherit from some other class, this syntax is definitely for you:
class PostsController < ApplicationController
resource_controller
end
Both syntaxes are identical in their behavior. Just make sure you call resource_controller before you use any other r_c functionality in your controller.
Nobody just uses the default RESTful controller, though. resource_controller provides a simple API for customizations.
== Action Lifecycle
It's really easy to make changes to the lifecycle of your actions.
Note: We had to call the new accessor "new_action", since new is somewhat reserved in ruby.
=== Before and After
class ProjectsController < ResourceController::Base
new_action.before do
3.times { object.tasks.build }
end
create.after do
object.creator = current_user
end
end
=== Flash
class ProjectsController < ResourceController::Base
create.flash "Can you believe how easy it is to use resource_controller? Neither could I!"
end
=== respond_to
You can add to what's already there...
class ProjectsController < ResourceController::Base
create.wants.js { render :template => "show.rjs" }
end
Or you can create a whole new block. This syntax destroys everything that's there, and starts again...
class ProjectsController < ResourceController::Base
create.response do |wants|
wants.html
wants.js { render :template => "show.rjs" }
end
end
If you have a nested resource and want to redirect to the parent after create/update and destroy you can do this in the object controller
class CommentsController < ResourceController::Base
belongs_to :post
create.wants.html { redirect_to smart_url(parent_url_options) }
update.wants.html { redirect_to smart_url(parent_url_options) }
destroy.wants.html { redirect_to smart_url(parent_url_options) }
end
=== Scoping
Because sometimes you want to make a bunch of customizations at once, most of the helpers accept blocks that make grouping calls really easy. Is it a DSL? Maybe; maybe not. But, it's definitely awesome.
With actions that can fail, the scoping defaults to success. That means that create.flash == create.success.flash.
class ProjectsController < ResourceController::Base
create do
flash "Object successfully created!"
wants.js { render :template => "show.rjs" }
failure.wants.js { render :template => "display_errors.rjs" }
end
destroy do
flash "You destroyed your project. Good work."
failure do
flash "You cannot destroy that project. Stop trying!"
wants.js { render :template => "display_errors.rjs" }
end
end
end
== Singleton Resource
If you want to create a singleton RESTful controller inherit from ResourceController::Singleton.
class AccountsController < ResourceController::Singleton
end
...or if need to inherit from some other class:
class AccountsController < ApplicationController
resource_controller :singleton
end
*Note:* This type of controllers handle a single resource only so the index action and all the collection helpers (collection_url, collection_path...) are not available for them.
Loading objects in singletons is similar to plural controllers with one exception. For non-nested singleton controllers you should override the object method as it defaults to nil for them.
class AccountsController < ResourceController::Singleton
private
def object
@object ||= Account.find(session[:account_id])
end
end
In other cases you can use the default logic and override it only if you use permalinks or anything special.
Singleton nesting with both :has_many and :has_one associations is provided...
map.resource :account, :has_many => :options # /account/options, account is a singleton parent
map.resources :users, :has_one => :image # /users/1/image, image is a singleton child
If you have the :has_many association with a singleton parent remember to override parent_object for your :has_many controller as it returns nil by default in this case.
class OptionsController < ResourceController::Base
belongs_to :account
protected
def parent_object
Account.find(session[:account_id])
end
end
== Helpers (ResourceController::Helpers)
=== Loading objects
You want to add something like pagination to your controller...
class PostsController < ResourceController::Base
private
def collection
@collection ||= end_of_association_chain.find(:all, :page => {:size => 10, :current => params[:page]})
end
end
Or maybe you used a permalink...
class PostsController < ResourceController::Base
private
def object
@object ||= end_of_association_chain.find_by_permalink(param)
end
end
=== Building objects
Maybe you have some alternative way of building objects...
class PostsController < ResourceController::Base
private
def build_object
@object ||= end_of_association_chain.build_my_object_some_funky_way object_params
end
end
...and there are tons more helpers in the ResourceController::Helpers
== Nested Resources
Nested controllers can be a pain, especially if routing is such that you may or may not have a parent. Not so with Resource Controller.
class CommentsController < ResourceController::Base
belongs_to :post
end
All of the finding, and creation, and everything will be done at the scope of the post automatically.
== Namespaced Resources
...are handled automatically, and any namespaces are always available, symbolized, in array form @ ResourceController::Helpers#namespaces
== Polymorphic Resources
Everything, including url generation is handled completely automatically. Take this example...
## comment.rb
class Comment
belongs_to :commentable, :polymorphic => true
end
## comments_controller.rb
class CommentsController < ResourceController::Base
belongs_to :post, :product, :user
end
*Note:* Your model doesn't have to be polymorphic in the ActiveRecord sense. It can be associated in whichever way you want.
## routes.rb
map.resources :posts, :has_many => :comments
map.resources :products, :has_many => :comments
map.resources :users, :has_many => :comments
All you have to do is that, and r_c will infer whichever relationship is present, and perform all the actions at the scope of the parent object.
=== Parent Helpers
You also get some helpers for reflecting on your parent.
parent? # => true/false is there a parent present?
parent_type # => :post
parent_model # => Post
parent_object # => @post
=== Non-standard resource names
resource_controller supports overrides for every non-standard configuration of resources.
The most common example is where the resource has a different name than the associated model. Simply overriding the model_name helper will get resource_controller working with your model.
map.resources :tags
...
class PhotoTag < ActiveRecord::Base
...
class TagsController < ResourceController::Base
private
def model_name
'photo_tag'
end
end
In the above example, the variable, and params will be set to @tag, @tags, and params[:tag]. If you'd like to change that, override object_name.
def object_name
'photo_tag'
end
If you're using a non-standard controller name, but everything else is standard, overriding resource_name will propagate through all of the other helpers.
map.resources :tags, :controller => "somethings"
...
class Tag < ActiveRecord::Base
...
class SomethingsController < ResourceController::Base
private
def resource_name
'tag'
end
end
Finally, the route_name helper is used by Urligence to determine which url helper to call, so if you have non-standard route names, override it.
map.resources :tags, :controller => "taggings"
...
class Taggings < ActiveRecord::Base
...
class TaggingsController < ResourceController::Base
private
def route_name
'tag'
end
end
== Url Helpers
Thanks to Urligence, you also get some free url helpers.
No matter what your controller looks like...
[edit_|new_]object_url # is the equivalent of saying [edit_|new_]post_url(@post)
[edit_|new_]object_url(some_other_object) # allows you to specify an object, but still maintain any paths or namespaces that are present
collection_url # is like saying posts_url
Url helpers are especially useful when working with polymorphic controllers.
# /posts/1/comments
object_url # => /posts/1/comments/#{@comment.to_param}
object_url(comment) # => /posts/1/comments/#{comment.to_param}
edit_object_url # => /posts/1/comments/#{@comment.to_param}/edit
collection_url # => /posts/1/comments
# /products/1/comments
object_url # => /products/1/comments/#{@comment.to_param}
object_url(comment) # => /products/1/comments/#{comment.to_param}
edit_object_url # => /products/1/comments/#{@comment.to_param}/edit
collection_url # => /products/1/comments
# /comments
object_url # => /comments/#{@comment.to_param}
object_url(comment) # => /comments/#{comment.to_param}
edit_object_url # => /comments/#{@comment.to_param}/edit
collection_url # => /comments
Or with namespaced, nested controllers...
# /admin/products/1/options
object_url # => /admin/products/1/options/#{@option.to_param}
object_url(option) # => /admin/products/1/options/#{option.to_param}
edit_object_url # => /admin/products/1/options/#{@option.to_param}/edit
collection_url # => /admin/products/1/options
You get the idea. Everything is automagical! All parameters are inferred.
== Credits
resource_controller was created, and is maintained by {James Golick}[http://jamesgolick.com].
== License
resource_controller is available under the {MIT License}[http://en.wikipedia.org/wiki/MIT_License]

View File

@@ -0,0 +1,50 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
begin
require 'jeweler'
Jeweler::Tasks.new do |s|
s.name = "resource_controller"
s.summary = "Rails RESTful controller abstraction plugin."
s.email = "james@giraffesoft.ca"
s.homepage = "http://jamesgolick.com/resource_controller"
s.description = ""
s.authors = ["James Golick"]
file_list = FileList.new("[A-Z]*.*", "{bin,generators,lib,test,spec,rails}/**/*") do |f|
f.exclude(/\.sqlite3/)
f.exclude(/\.log/)
end
s.files = file_list
end
rescue LoadError
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
end
desc 'Generate documentation for the ResourceController plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = '../rdoc'
rdoc.title = 'ResourceController'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('../README.rdoc')
rdoc.rdoc_files.include('../lib/**/*.rb')
end
task :rc_test do
Dir.chdir('test')
load 'Rakefile'
Rake::Task['test'].execute
end
task :upload_docs => :rdoc do
puts 'Deleting previous rdoc'
`ssh jamesgolick.com 'rm -Rf /home/apps/jamesgolick.com/public/resource_controller/rdoc'`
puts "Uploading current rdoc"
`scp -r ../rdoc jamesgolick.com:/home/apps/jamesgolick.com/public/resource_controller`
puts "Deleting rdoc"
`rm -Rf ../rdoc`
end
task :default => :rc_test

View File

@@ -0,0 +1,5 @@
---
:major: 0
:minor: 6
:build:
:patch: 6

View File

@@ -0,0 +1,29 @@
Description:
The scaffold resource generator creates a model, a controller, and a set of templates that's ready to use as the
starting point for your REST-like, resource-oriented application. This basically means that it follows a set of
conventions to exploit the full set of HTTP verbs (GET/POST/PUT/DELETE) and is prepared for multi-client access
(like one view for HTML, one for an XML API, one for ATOM, etc). Everything comes with sample unit and functional
tests as well.
The generator takes the name of the model as its first argument. This model name is then pluralized to get the
controller name. So "scaffold_resource post" will generate a Post model and a PostsController and will be intended
for URLs like /posts and /posts/45.
As additional parameters, the generator will take attribute pairs described by name and type. These attributes will
be used to prepopulate the migration to create the table for the model and to give you a set of templates for the
view. For example, "scaffold_resource post title:string created_on:date body:text published:boolean" will give
you a model with those four attributes, forms to create and edit those models from, and an index that'll list them
all.
You don't have to think up all attributes up front, but it's a good idea of adding just the baseline of what's
needed to start really working with the resource.
Once the generator has run, you'll need to add a declaration to your config/routes.rb file to hook up the rules
that'll point URLs to this new resource. If you create a resource like "scaffold_resource post", you'll need to
add "map.resources :posts" (notice the plural form) in the routes file. Then your new resource is accessible from
/posts.
Examples:
./script/generate scaffold_resource post # no attributes, view will be anemic
./script/generate scaffold_resource post title:string created_on:date body:text published:boolean
./script/generate scaffold_resource purchase order_id:integer created_at:datetime amount:decimal

View File

@@ -0,0 +1,179 @@
class ScaffoldResourceGenerator < Rails::Generator::NamedBase
attr_reader :controller_name,
:controller_class_path,
:controller_file_path,
:controller_class_nesting,
:controller_class_nesting_depth,
:controller_class_name,
:controller_singular_name,
:controller_plural_name,
:resource_edit_path,
:default_file_extension,
:generator_default_file_extension
alias_method :controller_file_name, :controller_singular_name
alias_method :controller_table_name, :controller_plural_name
def initialize(runtime_args, runtime_options = {})
super
if @rspec = has_rspec?
if ActionController::Base.respond_to?(:resource_action_separator)
@resource_edit_path = "/edit"
else
@resource_edit_path = ";edit"
end
end
@generator_default_file_extension = (defined? Haml )? "haml" : "erb"
# we want to call erb templates .rhtml or .haml if this is rails 1
if RAILS_GEM_VERSION.to_i == 1
@default_file_extension = @generator_default_file_extension == 'erb' ? 'rhtml' : @generator_default_file_extension
else
@default_file_extension = "html.#{@generator_default_file_extension}"
end
@controller_name = @name.pluralize
base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
@controller_class_name_without_nesting, @controller_singular_name, @controller_plural_name = inflect_names(base_name)
if @controller_class_nesting.empty?
@controller_class_name = @controller_class_name_without_nesting
else
@controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
end
end
def manifest
record do |m|
# Check for class naming collisions.
m.class_collisions(controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper")
m.class_collisions(class_path, "#{class_name}")
# Controller, helper, views, and test directories.
m.directory(File.join('app/models', class_path))
m.directory(File.join('app/controllers', controller_class_path))
m.directory(File.join('app/helpers', controller_class_path))
m.directory(File.join('app/views', controller_class_path, controller_file_name))
if @rspec
m.directory(File.join('spec/controllers', controller_class_path))
m.directory(File.join('spec/helpers', class_path))
m.directory(File.join('spec/models', class_path))
m.directory File.join('spec/views', controller_class_path, controller_file_name)
m.directory(File.join('spec/fixtures', class_path))
else
m.directory(File.join('test/functional', controller_class_path))
m.directory(File.join('test/unit', class_path))
end
scaffold_views.each do |action|
m.template(
"view_#{action}.#{generator_default_file_extension}",
File.join('app/views', controller_class_path, controller_file_name, "#{action}.#{default_file_extension}")
)
end
m.template('model.rb', File.join('app/models', class_path, "#{file_name}.rb"))
m.template('controller.rb', File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb"))
m.template('helper.rb', File.join('app/helpers', controller_class_path, "#{controller_file_name}_helper.rb"))
if @rspec
m.template('rspec/functional_spec.rb', File.join('spec/controllers', controller_class_path, "#{controller_file_name}_controller_spec.rb"))
m.template('rspec/routing_spec.rb', File.join('spec/controllers', controller_class_path, "#{controller_file_name}_routing_spec.rb"))
m.template('rspec/helper_spec.rb', File.join('spec/helpers', class_path, "#{controller_file_name}_helper_spec.rb"))
m.template('rspec/unit_spec.rb', File.join('spec/models', class_path, "#{file_name}_spec.rb"))
m.template('fixtures.yml', File.join('spec/fixtures', "#{table_name}.yml"))
rspec_views.each do |action|
m.template(
"rspec/views/#{action}_spec.rb",
File.join('spec/views', controller_class_path, controller_file_name, "#{action}_spec.rb")
)
end
else
functional_test = (defined? ThoughtBot::Shoulda) ? "shoulda_functional_test.rb" : "functional_test.rb"
m.template("#{functional_test}", File.join('test/functional', controller_class_path, "#{controller_file_name}_controller_test.rb"))
m.template('unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb"))
m.template('fixtures.yml', File.join('test/fixtures', "#{table_name}.yml"))
end
unless options[:skip_migration]
migration_template = RAILS_GEM_VERSION.to_i == 1 ? 'old_migration.rb' : 'migration.rb'
m.migration_template(
migration_template, 'db/migrate',
:assigns => {
:migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}",
:attributes => attributes
},
:migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
)
end
m.route_resources controller_file_name
end
end
# Lifted from Rick Olson's restful_authentication
def has_rspec?
options[:rspec] || (File.exist?('spec') && File.directory?('spec'))
end
protected
# Override with your own usage banner.
def banner
"Usage: #{$0} scaffold_resource ModelName [field:type, field:type]"
end
def rspec_views
%w[ index show new edit ]
end
def scaffold_views
rspec_views + %w[ _form ]
end
def model_name
class_name.demodulize
end
def add_options!(opt)
opt.separator ''
opt.separator 'Options:'
opt.on("--rspec", "Force rspec mode (checks for RAILS_ROOT/spec by default)") { |v| options[:rspec] = true }
end
end
module Rails
module Generator
class GeneratedAttribute
def default_value
@default_value ||= case type
when :int, :integer then "\"1\""
when :float then "\"1.5\""
when :decimal then "\"9.99\""
when :datetime, :timestamp, :time then "Time.now"
when :date then "Date.today"
when :string then "\"MyString\""
when :text then "\"MyText\""
when :boolean then "false"
else
""
end
end
def input_type
@input_type ||= case type
when :text then "textarea"
else
"input"
end
end
end
end
end

View File

@@ -0,0 +1,2 @@
class <%= controller_class_name %>Controller < ResourceController::Base
end

View File

@@ -0,0 +1,10 @@
one:
id: 1
<% for attribute in attributes -%>
<%= attribute.name %>: <%= attribute.default %>
<% end -%>
two:
id: 2
<% for attribute in attributes -%>
<%= attribute.name %>: <%= attribute.default %>
<% end -%>

View File

@@ -0,0 +1,57 @@
require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../test_helper'
require '<%= controller_file_path %>_controller'
# Re-raise errors caught by the controller.
class <%= controller_class_name %>Controller; def rescue_action(e) raise e end; end
class <%= controller_class_name %>ControllerTest < Test::Unit::TestCase
fixtures :<%= table_name %>
def setup
@controller = <%= controller_class_name %>Controller.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_should_get_index
get :index
assert_response :success
assert assigns(:<%= table_name %>)
end
def test_should_get_new
get :new
assert_response :success
end
def test_should_create_<%= file_name %>
old_count = <%= class_name %>.count
post :create, :<%= file_name %> => { }
assert_equal old_count+1, <%= class_name %>.count
assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>))
end
def test_should_show_<%= file_name %>
get :show, :id => 1
assert_response :success
end
def test_should_get_edit
get :edit, :id => 1
assert_response :success
end
def test_should_update_<%= file_name %>
put :update, :id => 1, :<%= file_name %> => { }
assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>))
end
def test_should_destroy_<%= file_name %>
old_count = <%= class_name %>.count
delete :destroy, :id => 1
assert_equal old_count-1, <%= class_name %>.count
assert_redirected_to <%= table_name %>_path
end
end

View File

@@ -0,0 +1,2 @@
module <%= controller_class_name %>Helper
end

View File

@@ -0,0 +1,15 @@
class <%= migration_name %> < ActiveRecord::Migration
def self.up
create_table :<%= table_name %>, :force => true do |t|
<% for attribute in attributes -%>
t.<%= attribute.type %> :<%= attribute.name %>
<% end -%>
t.timestamps
end
end
def self.down
drop_table :<%= table_name %>
end
end

View File

@@ -0,0 +1,2 @@
class <%= class_name %> < ActiveRecord::Base
end

View File

@@ -0,0 +1,13 @@
class <%= migration_name %> < ActiveRecord::Migration
def self.up
create_table :<%= table_name %>, :force => true do |t|
<% for attribute in attributes -%>
t.column :<%= attribute.name %>, :<%= attribute.type %>
<% end -%>
end
end
def self.down
drop_table :<%= table_name %>
end
end

View File

@@ -0,0 +1,255 @@
require File.expand_path(File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../spec_helper')
describe <%= controller_class_name %>Controller do
describe "handling GET /<%= table_name %>" do
before(:each) do
@<%= file_name %> = mock_model(<%= controller_class_name.singularize %>)
<%= controller_class_name.singularize %>.stub!(:find).and_return([@<%= file_name %>])
end
def do_get
get :index
end
it "should be successful" do
do_get
response.should be_success
end
it "should render index template" do
do_get
response.should render_template('index')
end
it "should find all <%= table_name %>" do
<%= controller_class_name.singularize %>.should_receive(:find).with(:all).and_return([@<%= file_name %>])
do_get
end
it "should assign the found <%= table_name %> for the view" do
do_get
assigns[:<%= table_name %>].should == [@<%= file_name %>]
end
end
describe "handling GET /<%= table_name %>/1" do
before(:each) do
@<%= file_name %> = mock_model(<%= controller_class_name.singularize %>)
<%= controller_class_name.singularize %>.stub!(:find).and_return(@<%= file_name %>)
end
def do_get
get :show, :id => "1"
end
it "should be successful" do
do_get
response.should be_success
end
it "should render show template" do
do_get
response.should render_template('show')
end
it "should find the <%= file_name %> requested" do
<%= controller_class_name.singularize %>.should_receive(:find).with("1").and_return(@<%= file_name %>)
do_get
end
it "should assign the found <%= file_name %> for the view" do
do_get
assigns[:<%= file_name %>].should equal(@<%= file_name %>)
end
end
describe "handling GET /<%= table_name %>/new" do
before(:each) do
@<%= file_name %> = mock_model(<%= controller_class_name.singularize %>)
<%= controller_class_name.singularize %>.stub!(:new).and_return(@<%= file_name %>)
end
def do_get
get :new
end
it "should be successful" do
do_get
response.should be_success
end
it "should render new template" do
do_get
response.should render_template('new')
end
it "should create an new <%= file_name %>" do
<%= controller_class_name.singularize %>.should_receive(:new).and_return(@<%= file_name %>)
do_get
end
it "should not save the new <%= file_name %>" do
@<%= file_name %>.should_not_receive(:save)
do_get
end
it "should assign the new <%= file_name %> for the view" do
do_get
assigns[:<%= file_name %>].should equal(@<%= file_name %>)
end
end
describe "handling GET /<%= table_name %>/1/edit" do
before(:each) do
@<%= file_name %> = mock_model(<%= controller_class_name.singularize %>)
<%= controller_class_name.singularize %>.stub!(:find).and_return(@<%= file_name %>)
end
def do_get
get :edit, :id => "1"
end
it "should be successful" do
do_get
response.should be_success
end
it "should render edit template" do
do_get
response.should render_template('edit')
end
it "should find the <%= file_name %> requested" do
<%= controller_class_name.singularize %>.should_receive(:find).and_return(@<%= file_name %>)
do_get
end
it "should assign the found <%= controller_class_name %> for the view" do
do_get
assigns[:<%= file_name %>].should equal(@<%= file_name %>)
end
end
describe "handling POST /<%= table_name %>" do
before(:each) do
@<%= file_name %> = mock_model(<%= controller_class_name.singularize %>, :to_param => "1")
<%= controller_class_name.singularize %>.stub!(:new).and_return(@<%= file_name %>)
end
describe "with successful save" do
def do_post
@<%= file_name %>.should_receive(:save).and_return(true)
post :create, :<%= file_name %> => {}
end
it "should create a new <%= file_name %>" do
<%= controller_class_name.singularize %>.should_receive(:new).with({}).and_return(@<%= file_name %>)
do_post
end
it "should redirect to the new <%= file_name %>" do
do_post
response.should redirect_to(<%= table_name.singularize %>_url("1"))
end
end
describe "with failed save" do
def do_post
@<%= file_name %>.should_receive(:save).and_return(false)
post :create, :<%= file_name %> => {}
end
it "should re-render 'new'" do
do_post
response.should render_template('new')
end
end
end
describe "handling PUT /<%= table_name %>/1" do
before(:each) do
@<%= file_name %> = mock_model(<%= controller_class_name.singularize %>, :to_param => "1")
<%= controller_class_name.singularize %>.stub!(:find).and_return(@<%= file_name %>)
end
describe "with successful update" do
def do_put
@<%= file_name %>.should_receive(:update_attributes).and_return(true)
put :update, :id => "1"
end
it "should find the <%= file_name %> requested" do
<%= controller_class_name.singularize %>.should_receive(:find).with("1").and_return(@<%= file_name %>)
do_put
end
it "should update the found <%= file_name %>" do
do_put
assigns(:<%= file_name %>).should equal(@<%= file_name %>)
end
it "should assign the found <%= file_name %> for the view" do
do_put
assigns(:<%= file_name %>).should equal(@<%= file_name %>)
end
it "should redirect to the <%= file_name %>" do
do_put
response.should redirect_to(<%= table_name.singularize %>_url("1"))
end
end
describe "with failed update" do
def do_put
@<%= file_name %>.should_receive(:update_attributes).and_return(false)
put :update, :id => "1"
end
it "should re-render 'edit'" do
do_put
response.should render_template('edit')
end
end
end
describe "handling DELETE /<%= table_name %>/1" do
before(:each) do
@<%= file_name %> = mock_model(<%= controller_class_name.singularize %>, :destroy => true)
<%= controller_class_name.singularize %>.stub!(:find).and_return(@<%= file_name %>)
end
def do_delete
delete :destroy, :id => "1"
end
it "should find the <%= file_name %> requested" do
<%= controller_class_name.singularize %>.should_receive(:find).with("1").and_return(@<%= file_name %>)
do_delete
end
it "should call destroy on the found <%= file_name %>" do
@<%= file_name %>.should_receive(:destroy).and_return(true)
do_delete
end
it "should redirect to the <%= table_name %> list" do
do_delete
response.should redirect_to(<%= table_name %>_url)
end
end
end

View File

@@ -0,0 +1,11 @@
require File.expand_path(File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../spec_helper')
describe <%= controller_class_name %>Helper do
#Delete this example and add some real ones or delete this file
it "should be included in the object returned by #helper" do
included_modules = (class << helper; self; end).send :included_modules
included_modules.should include(<%= controller_class_name %>Helper)
end
end

View File

@@ -0,0 +1,61 @@
require File.expand_path(File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../spec_helper')
describe <%= controller_class_name %>Controller do
describe "route generation" do
it "should map { :controller => '<%= table_name %>', :action => 'index' } to /<%= table_name %>" do
route_for(:controller => "<%= table_name %>", :action => "index").should == "/<%= table_name %>"
end
it "should map { :controller => '<%= table_name %>', :action => 'new' } to /<%= table_name %>/new" do
route_for(:controller => "<%= table_name %>", :action => "new").should == "/<%= table_name %>/new"
end
it "should map { :controller => '<%= table_name %>', :action => 'show', :id => '1'} to /<%= table_name %>/1" do
route_for(:controller => "<%= table_name %>", :action => "show", :id => "1").should == "/<%= table_name %>/1"
end
it "should map { :controller => '<%= table_name %>', :action => 'edit', :id => '1' } to /<%= table_name %>/1<%= resource_edit_path %>" do
route_for(:controller => "<%= table_name %>", :action => "edit", :id => "1").should == "/<%= table_name %>/1<%= resource_edit_path %>"
end
it "should map { :controller => '<%= table_name %>', :action => 'update', :id => '1' } to /<%= table_name %>/1" do
route_for(:controller => "<%= table_name %>", :action => "update", :id => "1").should == {:path => "/<%= table_name %>/1", :method => :put}
end
it "should map { :controller => '<%= table_name %>', :action => 'destroy', :id => '1' } to /<%= table_name %>/1" do
route_for(:controller => "<%= table_name %>", :action => "destroy", :id => "1").should == {:path => "/<%= table_name %>/1", :method => :delete}
end
end
describe "route recognition" do
it "should generate params { :controller => '<%= table_name %>', action => 'index' } from GET /<%= table_name %>" do
params_from(:get, "/<%= table_name %>").should == {:controller => "<%= table_name %>", :action => "index"}
end
it "should generate params { :controller => '<%= table_name %>', action => 'new' } from GET /<%= table_name %>/new" do
params_from(:get, "/<%= table_name %>/new").should == {:controller => "<%= table_name %>", :action => "new"}
end
it "should generate params { :controller => '<%= table_name %>', action => 'create' } from POST /<%= table_name %>" do
params_from(:post, "/<%= table_name %>").should == {:controller => "<%= table_name %>", :action => "create"}
end
it "should generate params { :controller => '<%= table_name %>', action => 'show', id => '1' } from GET /<%= table_name %>/1" do
params_from(:get, "/<%= table_name %>/1").should == {:controller => "<%= table_name %>", :action => "show", :id => "1"}
end
it "should generate params { :controller => '<%= table_name %>', action => 'edit', id => '1' } from GET /<%= table_name %>/1;edit" do
params_from(:get, "/<%= table_name %>/1<%= resource_edit_path %>").should == {:controller => "<%= table_name %>", :action => "edit", :id => "1"}
end
it "should generate params { :controller => '<%= table_name %>', action => 'update', id => '1' } from PUT /<%= table_name %>/1" do
params_from(:put, "/<%= table_name %>/1").should == {:controller => "<%= table_name %>", :action => "update", :id => "1"}
end
it "should generate params { :controller => '<%= table_name %>', action => 'destroy', id => '1' } from DELETE /<%= table_name %>/1" do
params_from(:delete, "/<%= table_name %>/1").should == {:controller => "<%= table_name %>", :action => "destroy", :id => "1"}
end
end
end

View File

@@ -0,0 +1,11 @@
require File.expand_path(File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../spec_helper')
describe <%= class_name %> do
before(:each) do
@<%= file_name %> = <%= class_name %>.new
end
it "should be valid" do
@<%= file_name %>.should be_valid
end
end

View File

@@ -0,0 +1,28 @@
require File.expand_path(File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper')
describe "/<%= table_name %>/edit.<%= default_file_extension %>" do
include <%= controller_class_name %>Helper
before do
@<%= file_name %> = mock_model(<%= class_name %>)
<% for attribute in attributes -%>
@<%= file_name %>.stub!(:<%= attribute.name %>).and_return(<%= attribute.default_value %>)
<% end -%>
assigns[:<%= file_name %>] = @<%= file_name %>
template.should_receive(:object_url).twice.and_return(<%= file_name %>_path(@<%= file_name %>))
template.should_receive(:collection_url).and_return(<%= file_name.pluralize %>_path)
end
it "should render edit form" do
render "/<%= table_name %>/edit.<%= default_file_extension %>"
response.should have_tag("form[action=#{<%= file_name %>_path(@<%= file_name %>)}][method=post]") do
<% for attribute in attributes -%><% unless attribute.name =~ /_id/ || [:datetime, :timestamp, :time, :date].index(attribute.type) -%>
with_tag('<%= attribute.input_type -%>#<%= file_name %>_<%= attribute.name %>[name=?]', "<%= file_name %>[<%= attribute.name %>]")
<% end -%><% end -%>
end
end
end

View File

@@ -0,0 +1,26 @@
require File.expand_path(File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper')
describe "/<%= table_name %>/index.<%= default_file_extension %>" do
include <%= controller_class_name %>Helper
before(:each) do
<% [98,99].each do |id| -%>
<%= file_name %>_<%= id %> = mock_model(<%= class_name %>)
<% for attribute in attributes -%>
<%= file_name %>_<%= id %>.should_receive(:<%= attribute.name %>).and_return(<%= attribute.default_value %>)
<% end -%><% end %>
assigns[:<%= table_name %>] = [<%= file_name %>_98, <%= file_name %>_99]
template.stub!(:object_url).and_return(<%= file_name %>_path(<%= file_name %>_99))
template.stub!(:new_object_url).and_return(new_<%= file_name %>_path)
template.stub!(:edit_object_url).and_return(edit_<%= file_name %>_path(<%= file_name %>_99))
end
it "should render list of <%= table_name %>" do
render "/<%= table_name %>/index.<%= default_file_extension %>"
<% for attribute in attributes -%><% unless attribute.name =~ /_id/ || [:datetime, :timestamp, :time, :date].index(attribute.type) -%>
response.should have_tag("tr>td", <%= attribute.default_value %>, 2)
<% end -%><% end -%>
end
end

View File

@@ -0,0 +1,30 @@
require File.expand_path(File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper')
describe "/<%= table_name %>/new.<%= default_file_extension %>" do
include <%= controller_class_name %>Helper
before(:each) do
@<%= file_name %> = mock_model(<%= class_name %>)
@<%= file_name %>.stub!(:new_record?).and_return(true)
<% for attribute in attributes -%>
@<%= file_name %>.stub!(:<%= attribute.name %>).and_return(<%= attribute.default_value %>)
<% end -%>
assigns[:<%= file_name %>] = @<%= file_name %>
template.stub!(:object_url).and_return(<%= file_name %>_path(@<%= file_name %>))
template.stub!(:collection_url).and_return(<%= file_name.pluralize %>_path)
end
it "should render new form" do
render "/<%= table_name %>/new.<%= default_file_extension %>"
response.should have_tag("form[action=?][method=post]", <%= table_name %>_path) do
<% for attribute in attributes -%><% unless attribute.name =~ /_id/ || [:datetime, :timestamp, :time, :date].index(attribute.type) -%>
with_tag("<%= attribute.input_type -%>#<%= file_name %>_<%= attribute.name %>[name=?]", "<%= file_name %>[<%= attribute.name %>]")
<% end -%><% end -%>
end
end
end

View File

@@ -0,0 +1,25 @@
require File.expand_path(File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../../spec_helper')
describe "/<%= table_name %>/show.<%= default_file_extension %>" do
include <%= controller_class_name %>Helper
before(:each) do
@<%= file_name %> = mock_model(<%= class_name %>)
<% for attribute in attributes -%>
@<%= file_name %>.stub!(:<%= attribute.name %>).and_return(<%= attribute.default_value %>)
<% end -%>
assigns[:<%= file_name %>] = @<%= file_name %>
template.stub!(:edit_object_url).and_return(edit_<%= file_name %>_path(@<%= file_name %>))
template.stub!(:collection_url).and_return(<%= file_name.pluralize %>_path)
end
it "should render attributes in <p>" do
render "/<%= table_name %>/show.<%= default_file_extension %>"
<% for attribute in attributes -%><% unless attribute.name =~ /_id/ || [:datetime, :timestamp, :time, :date].index(attribute.type) -%>
response.should have_text(/<%= Regexp.escape(attribute.default_value)[1..-2]%>/)
<% end -%><% end -%>
end
end

View File

@@ -0,0 +1,19 @@
require File.dirname(__FILE__) + '<%= '/..' * controller_class_nesting_depth %>/../test_helper'
require '<%= controller_file_path %>_controller'
# Re-raise errors caught by the controller.
class <%= controller_class_name %>Controller; def rescue_action(e) raise e end; end
class <%= controller_class_name %>ControllerTest < Test::Unit::TestCase
def setup
@controller = <%= controller_class_name %>Controller.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@<%= singular_name %> = <%= plural_name %> :one
end
should_be_restful do |resource|
resource.formats = [:html]
end
end

View File

@@ -0,0 +1,7 @@
require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper'
class <%= class_name %>Test < Test::Unit::TestCase
def test_truth
assert true
end
end

View File

@@ -0,0 +1,6 @@
<%- for attribute in attributes -%>
<p>
<label for="<%= singular_name %>_<%= attribute.name %>"><%= attribute.column.human_name %>:</label>
<%%= f.<%= attribute.field_type %> :<%= attribute.name %> %>
</p>
<% end -%>

View File

@@ -0,0 +1,5 @@
<%- for attribute in attributes -%>
%p
%label{:for => "<%= singular_name %>_<%= attribute.name %>"} <%= attribute.column.human_name %>:
= f.<%= attribute.field_type %> :<%= attribute.name %>
<% end -%>

View File

@@ -0,0 +1,16 @@
<h1>Editing <%= singular_name.titleize %></h1>
<%%= error_messages_for :<%= singular_name %> %>
<%% form_for(:<%= singular_name %>, :url => object_url, :html => { :method => :put }) do |f| %>
<%%= render :partial => "form", :locals => { :f => f } %>
<p>
<%%=submit_tag "Update"%>
</p>
<%% end %>
<br/>
<%%= link_to 'Show', object_url %>
|
<%%= link_to 'Back', collection_url %>

View File

@@ -0,0 +1,11 @@
%h1 Editing <%= singular_name %>
= error_messages_for :<%= singular_name %>
- form_for(:<%= singular_name %>, :url => object_url, :html => { :method => :put }) do |f|
= render :partial => "form", :locals => { :f => f }
%p= submit_tag "Update"
= link_to 'Show', object_url
|
= link_to 'Back', collection_url

View File

@@ -0,0 +1,22 @@
<h1>Listing <%= plural_name.titleize %></h1>
<table>
<tr>
<% for attribute in attributes -%>
<th><%= attribute.column.human_name %></th>
<% end -%>
</tr>
<%%- @<%= plural_name %>.each do |<%= singular_name %>|%>
<tr>
<%- for attribute in attributes -%>
<td><%%=h <%= singular_name %>.<%= attribute.name %> %></td>
<%- end -%>
<td><%%=link_to 'Show', object_url(<%= singular_name %>) %></td>
<td><%%=link_to 'Edit', edit_object_url(<%= singular_name %>) %></td>
<td><%%=link_to 'Destroy', object_url(<%= singular_name %>), :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<%% end %>
</table>
<br/>
<%%= link_to 'New <%= singular_name.titleize %>', new_object_url %>

View File

@@ -0,0 +1,19 @@
%h1 Listing <%= plural_name %>
%table
%tr
<% for attribute in attributes -%>
%th <%= attribute.column.human_name %>
<% end -%>
- @<%= plural_name %>.each do |<%= singular_name %>|
%tr
<% for attribute in attributes -%>
%td= h <%= singular_name %>.<%= attribute.name %>
<% end -%>
%td= link_to 'Show', object_url(<%= singular_name %>)
%td= link_to 'Edit', edit_object_url(<%= singular_name %>)
%td= link_to 'Destroy', object_url(<%= singular_name %>), :confirm => 'Are you sure?', :method => :delete
%br/
=link_to 'New <%= singular_name %>', new_object_url

View File

@@ -0,0 +1,12 @@
<h1>New <%= singular_name.titleize %></h1>
<%%= error_messages_for :<%= singular_name %> %>
<%% form_for(:<%= singular_name %>, :url => collection_url) do |f| %>
<%%= render :partial => "form", :locals => { :f => f } %>
<p>
<%%= submit_tag "Create" %>
</p>
<%% end %>
<br/>
<%%= link_to 'Back', collection_url %>

View File

@@ -0,0 +1,9 @@
%h1 New <%= singular_name %>
= error_messages_for :<%= singular_name %>
- form_for(:<%= singular_name %>, :url => collection_url) do |f|
= render :partial => "form", :locals => { :f => f }
%p= submit_tag "Create"
= link_to 'Back', collection_url

View File

@@ -0,0 +1,9 @@
<% for attribute in attributes -%>
<p>
<strong><%= attribute.column.human_name %>:</strong><%%=h @<%= singular_name %>.<%= attribute.name %> %>
</p>
<% end -%>
<%%= link_to 'Edit', edit_object_url %>
|
<%%= link_to 'Back', collection_url %>

View File

@@ -0,0 +1,9 @@
<% for attribute in attributes -%>
%p
%strong <%= attribute.column.human_name %>:
=h @<%= singular_name %>.<%= attribute.name %>
<% end -%>
= link_to 'Edit', edit_object_url
|
= link_to 'Back', collection_url

View File

@@ -0,0 +1 @@
require File.dirname(__FILE__)+'/rails/init.rb'

View File

@@ -0,0 +1 @@
# Install hook code here

View File

@@ -0,0 +1,26 @@
begin
require_dependency 'application_controller'
rescue LoadError => e
require_dependency 'application'
end
module ResourceController
ACTIONS = [:index, :show, :new_action, :create, :edit, :update, :destroy].freeze
SINGLETON_ACTIONS = (ACTIONS - [:index]).freeze
FAILABLE_ACTIONS = ACTIONS - [:index, :new_action, :edit].freeze
NAME_ACCESSORS = [:model_name, :route_name, :object_name]
module ActionControllerExtension
unloadable
def resource_controller(*args)
include ResourceController::Controller
if args.include?(:singleton)
include ResourceController::Helpers::SingletonCustomizations
end
end
end
end
require File.dirname(__FILE__)+'/../rails/init.rb' unless ActionController::Base.include?(Urligence)

View File

@@ -0,0 +1,77 @@
module ResourceController # :nodoc:
module Accessors # :nodoc:
private
def block_accessor(*accessors)
accessors.each do |block_accessor|
class_eval <<-"end_eval", __FILE__, __LINE__
def #{block_accessor}(*args, &block)
unless args.empty? && block.nil?
args.push block if block_given?
@#{block_accessor} = [args].flatten
end
@#{block_accessor}
end
end_eval
end
end
def scoping_reader(*accessor_names)
accessor_names.each do |accessor_name|
class_eval <<-"end_eval", __FILE__, __LINE__
def #{accessor_name}(&block)
@#{accessor_name}.instance_eval &block if block_given?
@#{accessor_name}
end
end_eval
end
end
def class_scoping_reader(accessor_name, start_value)
write_inheritable_attribute accessor_name, start_value
class_eval <<-"end_eval", __FILE__, __LINE__
def self.#{accessor_name}(&block)
read_inheritable_attribute(:#{accessor_name}).instance_eval(&block) if block_given?
read_inheritable_attribute(:#{accessor_name})
end
end_eval
end
def reader_writer(accessor_name)
class_eval <<-"end_eval", __FILE__, __LINE__
def #{accessor_name}(*args, &block)
args << block unless block.nil?
@#{accessor_name} = args.first unless args.empty?
@#{accessor_name}
end
end_eval
end
def class_reader_writer(*accessor_names)
accessor_names.each do |accessor_name|
class_eval <<-"end_eval", __FILE__, __LINE__
def self.#{accessor_name}(*args)
unless args.empty?
write_inheritable_attribute :#{accessor_name}, args.first if args.length == 1
write_inheritable_attribute :#{accessor_name}, args if args.length > 1
end
read_inheritable_attribute :#{accessor_name}
end
def #{accessor_name}(*args)
unless args.empty?
self.class.write_inheritable_attribute :#{accessor_name}, args.first if args.length == 1
self.class.write_inheritable_attribute :#{accessor_name}, args if args.length > 1
end
self.class.read_inheritable_attribute :#{accessor_name}
end
end_eval
end
end
end
end

View File

@@ -0,0 +1,40 @@
module ResourceController
class ActionOptions
extend ResourceController::Accessors
reader_writer :flash
reader_writer :flash_now
block_accessor :after, :before
def initialize
@collector = ResourceController::ResponseCollector.new
end
def response(*args, &block)
if !args.empty? || block_given?
@collector.clear
args.flatten.each { |symbol| @collector.send(symbol) }
block.call(@collector) if block_given?
end
@collector.responses
end
alias_method :respond_to, :response
alias_method :responds_to, :response
def wants
@collector
end
def dup
returning self.class.new do |duplicate|
duplicate.instance_variable_set(:@collector, wants.dup)
duplicate.instance_variable_set(:@before, before.dup) unless before.nil?
duplicate.instance_variable_set(:@after, after.dup) unless after.nil?
duplicate.instance_variable_set(:@flash, flash.dup) unless flash.nil?
duplicate.instance_variable_set(:@flash_now, flash_now.dup) unless flash_now.nil?
end
end
end
end

View File

@@ -0,0 +1,75 @@
module ResourceController
module Actions
def index
load_collection
before :index
response_for :index
end
def show
load_object
before :show
response_for :show
rescue ActiveRecord::RecordNotFound
response_for :show_fails
end
def create
build_object
load_object
before :create
if object.save
after :create
set_flash :create
response_for :create
else
after :create_fails
set_flash :create_fails
response_for :create_fails
end
end
def update
load_object
before :update
if object.update_attributes object_params
after :update
set_flash :update
response_for :update
else
after :update_fails
set_flash :update_fails
response_for :update_fails
end
end
def new
build_object
load_object
before :new_action
response_for :new_action
end
def edit
load_object
before :edit
response_for :edit
end
def destroy
load_object
before :destroy
if object.destroy
after :destroy
set_flash :destroy
response_for :destroy
else
after :destroy_fails
set_flash :destroy_fails
response_for :destroy_fails
end
end
end
end

View File

@@ -0,0 +1,15 @@
module ResourceController
# == ResourceController::Base
#
# Inherit from this class to create your RESTful controller. See the README for usage.
#
class Base < ::ApplicationController
unloadable
def self.inherited(subclass)
super
subclass.class_eval { resource_controller }
end
end
end

View File

@@ -0,0 +1,24 @@
module ResourceController
module ClassMethods
# Use this method in your controller to specify which actions you'd like it to respond to.
#
# class PostsController < ResourceController::Base
# actions :all, :except => :create
# end
def actions(*opts)
config = {}
config.merge!(opts.pop) if opts.last.is_a?(Hash)
all_actions = (singleton? ? ResourceController::SINGLETON_ACTIONS : ResourceController::ACTIONS) - [:new_action] + [:new]
actions_to_remove = []
actions_to_remove += all_actions - opts unless opts.first == :all
actions_to_remove += [*config[:except]] if config[:except]
actions_to_remove.uniq!
actions_to_remove.each { |action| undef_method(action) if method_defined?(action) }
end
end
end

View File

@@ -0,0 +1,70 @@
module ResourceController
module Controller
def self.included(subclass)
subclass.class_eval do
include ResourceController::Helpers
include ResourceController::Actions
extend ResourceController::Accessors
extend ResourceController::ClassMethods
class_reader_writer :belongs_to, *NAME_ACCESSORS
NAME_ACCESSORS.each { |accessor| send(accessor, controller_name.singularize.underscore) }
ACTIONS.each do |action|
class_scoping_reader action, FAILABLE_ACTIONS.include?(action) ? ResourceController::FailableActionOptions.new : ResourceController::ActionOptions.new
end
self.helper_method :object_url, :edit_object_url, :new_object_url, :collection_url, :object, :collection,
:parent, :parent_type, :parent_object, :parent_model, :model_name, :model, :object_path,
:edit_object_path, :new_object_path, :collection_path, :hash_for_collection_path, :hash_for_object_path,
:hash_for_edit_object_path, :hash_for_new_object_path, :hash_for_collection_url,
:hash_for_object_url, :hash_for_edit_object_url, :hash_for_new_object_url, :parent?,
:collection_url_options, :object_url_options, :new_object_url_options
end
init_default_actions(subclass)
end
private
def self.init_default_actions(klass)
klass.class_eval do
index.wants.html
edit.wants.html
new_action.wants.html
show do
wants.html
failure.wants.html { render :text => "Member object not found." }
end
create do
flash "Successfully created!"
wants.html { redirect_to object_url }
failure.wants.html { render :action => "new" }
end
update do
flash "Successfully updated!"
wants.html { redirect_to object_url }
failure.wants.html { render :action => "edit" }
end
destroy do
flash "Successfully removed!"
wants.html { redirect_to collection_url }
failure.wants.html { redirect_to object_url }
end
class << self
def singleton?
false
end
end
end
end
end
end

View File

@@ -0,0 +1,25 @@
module ResourceController
class FailableActionOptions
extend ResourceController::Accessors
scoping_reader :success, :fails
alias_method :failure, :fails
block_accessor :before
def initialize
@success = ActionOptions.new
@fails = ActionOptions.new
end
delegate :flash, :flash_now, :after, :response, :wants, :to => :success
def dup
returning self.class.new do |duplicate|
duplicate.instance_variable_set(:@success, success.dup)
duplicate.instance_variable_set(:@fails, fails.dup)
duplicate.instance_variable_set(:@before, before.dup) unless before.nil?
end
end
end
end

View File

@@ -0,0 +1,28 @@
module ResourceController
# == ResourceController::Helpers
#
# Included in Base.
#
# These helpers are used internally to manage objects, generate urls, and manage parent resource associations.
#
# If you want to customize certain controller behaviour, like member-object, and collection fetching, overriding these methods is all it takes.
#
# See the docs below, and the README for examples
#
# *Please Note: many of these helpers build on top of each other, and require that behaviour to be maintained, in order for other functionality to work properly.*
#
# e.g. All fetching must be done on top of the method end_of_association_chain, or else parent resources (including polymorphic ones) won't function correctly.
#
# class PostsController < ResourceController::Base
# private
# def object
# @object ||= end_of_association_chain.find_by_permalink(param)
# end
# end
module Helpers
include ResourceController::Helpers::Urls
include ResourceController::Helpers::Internal
include ResourceController::Helpers::Nested
include ResourceController::Helpers::CurrentObjects
end
end

View File

@@ -0,0 +1,73 @@
module ResourceController
module Helpers
module CurrentObjects
protected
# Used internally to return the model for your resource.
#
def model
model_name.to_s.camelize.constantize
end
# Used to fetch the collection for the index method
#
# In order to customize the way the collection is fetched, to add something like pagination, for example, override this method.
#
def collection
end_of_association_chain.find(:all)
end
# Returns the current param.
#
# Defaults to params[:id].
#
# Override this method if you'd like to use an alternate param name.
#
def param
params[:id]
end
# Used to fetch the current member object in all of the singular methods that operate on an existing member.
#
# Override this method if you'd like to fetch your objects in some alternate way, like using a permalink.
#
# class PostsController < ResourceController::Base
# private
# def object
# @object ||= end_of_association_chain.find_by_permalink(param)
# end
# end
#
def object
@object ||= end_of_association_chain.find(param) unless param.nil?
@object
end
# Used internally to load the member object in to an instance variable @#{model_name} (i.e. @post)
#
def load_object
instance_variable_set "@#{parent_type}", parent_object if parent?
instance_variable_set "@#{object_name}", object
end
# Used internally to load the collection in to an instance variable @#{model_name.pluralize} (i.e. @posts)
#
def load_collection
instance_variable_set "@#{parent_type}", parent_object if parent?
instance_variable_set "@#{object_name.to_s.pluralize}", collection
end
# Returns the form params. Defaults to params[model_name] (i.e. params["post"])
#
def object_params
params["#{object_name}"]
end
# Builds the object, but doesn't save it, during the new, and create action.
#
def build_object
@object ||= end_of_association_chain.send parent? ? :build : :new, object_params
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More