Organizando plugins e gems.
This commit is contained in:
5
vendor/plugins/acts_as_paranoid/CHANGELOG
vendored
5
vendor/plugins/acts_as_paranoid/CHANGELOG
vendored
@@ -1,3 +1,8 @@
|
||||
* (16 Apr 2009)
|
||||
|
||||
Allow :with_deleted and :only_deleted options to work with count and calculate.
|
||||
Fixes compatibility with will_paginate. [James Le Cuirot]
|
||||
|
||||
* (4 Oct 2007)
|
||||
|
||||
Update for Edge rails: remove support for legacy #count args
|
||||
|
||||
25
vendor/plugins/acts_as_paranoid/README
vendored
25
vendor/plugins/acts_as_paranoid/README
vendored
@@ -1,26 +1,5 @@
|
||||
= acts_as_paranoid
|
||||
|
||||
Overrides some basic methods for the current model so that calling #destroy sets a 'deleted_at' field to the
|
||||
current timestamp. ActiveRecord is required.
|
||||
Overrides some basic methods for the current model so that calling #destroy sets a 'deleted_at' field to the current timestamp. ActiveRecord is required.
|
||||
|
||||
== Resources
|
||||
|
||||
Install
|
||||
|
||||
* gem install acts_as_paranoid
|
||||
|
||||
Rubyforge project
|
||||
|
||||
* http://rubyforge.org/projects/ar-paranoid
|
||||
|
||||
RDocs
|
||||
|
||||
* http://ar-paranoid.rubyforge.org
|
||||
|
||||
Subversion
|
||||
|
||||
* http://techno-weenie.net/svn/projects/acts_as_paranoid
|
||||
|
||||
Collaboa
|
||||
|
||||
* http://collaboa.techno-weenie.net/repository/browse/acts_as_paranoid
|
||||
http://github.com/technoweenie/acts_as_paranoid
|
||||
@@ -1,41 +1,10 @@
|
||||
== Creating the test database
|
||||
1. Pick Rails version. Either dump this plugin in a Rails app and run it from there, or specify it as an ENV var:
|
||||
|
||||
The default name for the test databases is "activerecord_paranoid". If you
|
||||
want to use another database name then be sure to update the connection
|
||||
adapter setups you want to test with in test/connections/<your database>/connection.rb.
|
||||
When you have the database online, you can import the fixture tables with
|
||||
the test/fixtures/db_definitions/*.sql files.
|
||||
RAILS=2.2.2 rake
|
||||
RAILS=2.2.2 ruby test/paranoid_test.rb
|
||||
|
||||
Make sure that you create database objects with the same user that you specified in i
|
||||
connection.rb otherwise (on Postgres, at least) tests for default values will fail.
|
||||
2. Setup your database. By default sqlite3 is used, and no further setup is necessary. You can pick any of the listed databases in test/database.yml. Be sure to create the database first.
|
||||
|
||||
== Running with Rake
|
||||
DB=mysql rake
|
||||
|
||||
The easiest way to run the unit tests is through Rake. The default task runs
|
||||
the entire test suite for all the adapters. You can also run the suite on just
|
||||
one adapter by using the tasks test_mysql_ruby, test_ruby_mysql, test_sqlite,
|
||||
or test_postresql. For more information, checkout the full array of rake tasks with "rake -T"
|
||||
|
||||
Rake can be found at http://rake.rubyforge.org
|
||||
|
||||
== Running by hand
|
||||
|
||||
Unit tests are located in test directory. If you only want to run a single test suite,
|
||||
or don't want to bother with Rake, you can do so with something like:
|
||||
|
||||
cd test; ruby -I "connections/native_mysql" base_test.rb
|
||||
|
||||
That'll run the base suite using the MySQL-Ruby adapter. Change the adapter
|
||||
and test suite name as needed.
|
||||
|
||||
== Faster tests
|
||||
|
||||
If you are using a database that supports transactions, you can set the
|
||||
"AR_TX_FIXTURES" environment variable to "yes" to use transactional fixtures.
|
||||
This gives a very large speed boost. With rake:
|
||||
|
||||
rake AR_TX_FIXTURES=yes
|
||||
|
||||
Or, by hand:
|
||||
|
||||
AR_TX_FIXTURES=yes ruby -I connections/native_sqlite3 base_test.rb
|
||||
3. Profit!!
|
||||
20
vendor/plugins/acts_as_paranoid/init.rb
vendored
20
vendor/plugins/acts_as_paranoid/init.rb
vendored
@@ -11,6 +11,24 @@ class << ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def has_many_without_deleted(association_id, options = {}, &extension)
|
||||
with_deleted = options.delete :with_deleted
|
||||
returning has_many_with_deleted(association_id, options, &extension) do
|
||||
if options[:through] && !with_deleted
|
||||
reflection = reflect_on_association(association_id)
|
||||
collection_reader_method(reflection, Caboose::Acts::HasManyThroughWithoutDeletedAssociation)
|
||||
collection_accessor_methods(reflection, Caboose::Acts::HasManyThroughWithoutDeletedAssociation, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias_method_chain :belongs_to, :deleted
|
||||
alias_method :has_many_with_deleted, :has_many
|
||||
alias_method :has_many, :has_many_without_deleted
|
||||
alias_method :exists_with_deleted?, :exists?
|
||||
end
|
||||
ActiveRecord::Base.send :include, Caboose::Acts::Paranoid
|
||||
ActiveRecord::Base.send :include, Caboose::Acts::ParanoidFindWrapper
|
||||
class << ActiveRecord::Base
|
||||
alias_method_chain :acts_as_paranoid, :find_wrapper
|
||||
end
|
||||
ActiveRecord::Base.send :include, Caboose::Acts::Paranoid
|
||||
27
vendor/plugins/acts_as_paranoid/lib/caboose/acts/has_many_through_without_deleted_association.rb
vendored
Normal file
27
vendor/plugins/acts_as_paranoid/lib/caboose/acts/has_many_through_without_deleted_association.rb
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
module Caboose # :nodoc:
|
||||
module Acts # :nodoc:
|
||||
class HasManyThroughWithoutDeletedAssociation < ActiveRecord::Associations::HasManyThroughAssociation
|
||||
protected
|
||||
def current_time
|
||||
ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
|
||||
end
|
||||
|
||||
def construct_conditions
|
||||
return super unless @reflection.through_reflection.klass.paranoid?
|
||||
table_name = @reflection.through_reflection.table_name
|
||||
conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
|
||||
"#{table_name}.#{attr} = #{value}"
|
||||
end
|
||||
|
||||
deleted_attribute = @reflection.through_reflection.klass.deleted_attribute
|
||||
quoted_current_time = @reflection.through_reflection.klass.quote_value(
|
||||
current_time,
|
||||
@reflection.through_reflection.klass.columns_hash[deleted_attribute.to_s])
|
||||
conditions << "#{table_name}.#{deleted_attribute} IS NULL OR #{table_name}.#{deleted_attribute} > #{quoted_current_time}"
|
||||
|
||||
conditions << sql_conditions if sql_conditions
|
||||
"(" + conditions.join(') AND (') + ")"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,8 +16,8 @@ module Caboose #:nodoc:
|
||||
# Widget.find_with_deleted(:all)
|
||||
# # SELECT * FROM widgets
|
||||
#
|
||||
# Widget.find(:all, :with_deleted => true)
|
||||
# # SELECT * FROM widgets
|
||||
# Widget.find_only_deleted(:all)
|
||||
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NOT NULL
|
||||
#
|
||||
# Widget.find_with_deleted(1).deleted?
|
||||
# # Returns true if the record was previously destroyed, false if not
|
||||
@@ -31,6 +31,9 @@ module Caboose #:nodoc:
|
||||
# Widget.count_with_deleted
|
||||
# # SELECT COUNT(*) FROM widgets
|
||||
#
|
||||
# Widget.count_only_deleted
|
||||
# # SELECT COUNT(*) FROM widgets WHERE widgets.deleted_at IS NOT NULL
|
||||
#
|
||||
# Widget.delete_all
|
||||
# # UPDATE widgets SET deleted_at = '2005-09-17 17:46:36'
|
||||
#
|
||||
@@ -87,16 +90,49 @@ module Caboose #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
def find_only_deleted(*args)
|
||||
options = args.extract_options!
|
||||
validate_find_options(options)
|
||||
set_readonly_option!(options)
|
||||
options[:only_deleted] = true # yuck!
|
||||
|
||||
case args.first
|
||||
when :first then find_initial(options)
|
||||
when :all then find_every(options)
|
||||
else find_from_ids(args, options)
|
||||
end
|
||||
end
|
||||
|
||||
def exists?(*args)
|
||||
with_deleted_scope { exists_with_deleted?(*args) }
|
||||
end
|
||||
|
||||
def exists_only_deleted?(*args)
|
||||
with_only_deleted_scope { exists_with_deleted?(*args) }
|
||||
end
|
||||
|
||||
def count_with_deleted(*args)
|
||||
calculate_with_deleted(:count, *construct_count_options_from_args(*args))
|
||||
end
|
||||
|
||||
def count_only_deleted(*args)
|
||||
with_only_deleted_scope { count_with_deleted(*args) }
|
||||
end
|
||||
|
||||
def count(*args)
|
||||
with_deleted_scope { count_with_deleted(*args) }
|
||||
with, only = extract_deleted_options(args.last) if args.last.is_a?(Hash)
|
||||
|
||||
with ? count_with_deleted(*args) :
|
||||
only ? count_only_deleted(*args) :
|
||||
with_deleted_scope { count_with_deleted(*args) }
|
||||
end
|
||||
|
||||
def calculate(*args)
|
||||
with_deleted_scope { calculate_with_deleted(*args) }
|
||||
with, only = extract_deleted_options(args.last) if args.last.is_a?(Hash)
|
||||
|
||||
with ? calculate_with_deleted(*args) :
|
||||
only ? calculate_only_deleted(*args) :
|
||||
with_deleted_scope { calculate_with_deleted(*args) }
|
||||
end
|
||||
|
||||
def delete_all(conditions = nil)
|
||||
@@ -112,18 +148,28 @@ module Caboose #:nodoc:
|
||||
with_scope({:find => { :conditions => ["#{table_name}.#{deleted_attribute} IS NULL OR #{table_name}.#{deleted_attribute} > ?", current_time] } }, :merge, &block)
|
||||
end
|
||||
|
||||
def with_only_deleted_scope(&block)
|
||||
with_scope({:find => { :conditions => ["#{table_name}.#{deleted_attribute} IS NOT NULL AND #{table_name}.#{deleted_attribute} <= ?", current_time] } }, :merge, &block)
|
||||
end
|
||||
|
||||
private
|
||||
# all find calls lead here
|
||||
def find_every(options)
|
||||
options.delete(:with_deleted) ?
|
||||
find_every_with_deleted(options) :
|
||||
with_deleted_scope { find_every_with_deleted(options) }
|
||||
with, only = extract_deleted_options(options)
|
||||
|
||||
with ? find_every_with_deleted(options) :
|
||||
only ? with_only_deleted_scope { find_every_with_deleted(options) } :
|
||||
with_deleted_scope { find_every_with_deleted(options) }
|
||||
end
|
||||
|
||||
def extract_deleted_options(options)
|
||||
return options.delete(:with_deleted), options.delete(:only_deleted)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_without_callbacks
|
||||
unless new_record?
|
||||
self.class.update_all self.class.send(:sanitize_sql, ["#{self.class.deleted_attribute} = ?", self.class.send(:current_time)]), ["#{self.class.primary_key} = ?", id]
|
||||
self.class.update_all self.class.send(:sanitize_sql, ["#{self.class.deleted_attribute} = ?", (self.deleted_at = self.class.send(:current_time))]), ["#{self.class.primary_key} = ?", id]
|
||||
end
|
||||
freeze
|
||||
end
|
||||
@@ -143,11 +189,19 @@ module Caboose #:nodoc:
|
||||
!!read_attribute(:deleted_at)
|
||||
end
|
||||
|
||||
def restore!
|
||||
def recover!
|
||||
self.deleted_at = nil
|
||||
self.save!
|
||||
save!
|
||||
end
|
||||
|
||||
def recover_with_associations!(*associations)
|
||||
self.recover!
|
||||
associations.to_a.each do |assoc|
|
||||
self.send(assoc).find_with_deleted(:all).each do |a|
|
||||
a.recover! if a.class.paranoid?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
94
vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid_find_wrapper.rb
vendored
Normal file
94
vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid_find_wrapper.rb
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
module Caboose #:nodoc:
|
||||
module Acts #:nodoc:
|
||||
# Adds a wrapper find method which can identify :with_deleted or :only_deleted options
|
||||
# and would call the corresponding acts_as_paranoid finders find_with_deleted or
|
||||
# find_only_deleted methods.
|
||||
#
|
||||
# With this wrapper you can easily change from using this pattern:
|
||||
#
|
||||
# if some_condition_enabling_access_to_deleted_records?
|
||||
# @post = Post.find_with_deleted(params[:id])
|
||||
# else
|
||||
# @post = Post.find(params[:id])
|
||||
# end
|
||||
#
|
||||
# to this:
|
||||
#
|
||||
# @post = Post.find(params[:id], :with_deleted => some_condition_enabling_access_to_deleted_records?)
|
||||
#
|
||||
# Examples
|
||||
#
|
||||
# class Widget < ActiveRecord::Base
|
||||
# acts_as_paranoid
|
||||
# end
|
||||
#
|
||||
# Widget.find(:all)
|
||||
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL
|
||||
#
|
||||
# Widget.find(:all, :with_deleted => false)
|
||||
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL
|
||||
#
|
||||
# Widget.find_with_deleted(:all)
|
||||
# # SELECT * FROM widgets
|
||||
#
|
||||
# Widget.find(:all, :with_deleted => true)
|
||||
# # SELECT * FROM widgets
|
||||
#
|
||||
# Widget.find_only_deleted(:all)
|
||||
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NOT NULL
|
||||
#
|
||||
# Widget.find(:all, :only_deleted => true)
|
||||
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NOT NULL
|
||||
#
|
||||
# Widget.find(:all, :only_deleted => false)
|
||||
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL
|
||||
#
|
||||
module ParanoidFindWrapper
|
||||
def self.included(base) # :nodoc:
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def acts_as_paranoid_with_find_wrapper(options = {})
|
||||
unless paranoid? # don't let AR call this twice
|
||||
acts_as_paranoid_without_find_wrapper(options)
|
||||
class << self
|
||||
alias_method :find_without_find_wrapper, :find
|
||||
alias_method :validate_find_options_without_find_wrapper, :validate_find_options
|
||||
end
|
||||
end
|
||||
include InstanceMethods
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods #:nodoc:
|
||||
def self.included(base) # :nodoc:
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# This is a wrapper for the regular "find" so you can pass acts_as_paranoid related
|
||||
# options and determine which finder to call.
|
||||
def find(*args)
|
||||
options = args.extract_options!
|
||||
# Determine who to call.
|
||||
finder_option = VALID_PARANOID_FIND_OPTIONS.detect { |key| options.delete(key) } || :without_find_wrapper
|
||||
finder_method = "find_#{finder_option}".to_sym
|
||||
# Put back the options in the args now that they don't include the extended keys.
|
||||
args << options
|
||||
send(finder_method, *args)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
VALID_PARANOID_FIND_OPTIONS = [:with_deleted, :only_deleted]
|
||||
|
||||
def validate_find_options(options) #:nodoc:
|
||||
cleaned_options = options.reject { |k, v| VALID_PARANOID_FIND_OPTIONS.include?(k) }
|
||||
validate_find_options_without_find_wrapper(cleaned_options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
9
vendor/plugins/acts_as_paranoid/test/fixtures/taggings.yml
vendored
Normal file
9
vendor/plugins/acts_as_paranoid/test/fixtures/taggings.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
tagging_1:
|
||||
id: 1
|
||||
tag_id: 1
|
||||
widget_id: 1
|
||||
deleted_at: '2005-01-01 00:00:00'
|
||||
tagging_2:
|
||||
id: 2
|
||||
tag_id: 2
|
||||
widget_id: 1
|
||||
6
vendor/plugins/acts_as_paranoid/test/fixtures/tags.yml
vendored
Normal file
6
vendor/plugins/acts_as_paranoid/test/fixtures/tags.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
tag_1:
|
||||
id: 1
|
||||
name: 'tag 1'
|
||||
tag_2:
|
||||
id: 2
|
||||
name: 'tag 1'
|
||||
@@ -6,6 +6,9 @@ class Widget < ActiveRecord::Base
|
||||
has_and_belongs_to_many :habtm_categories, :class_name => 'Category'
|
||||
has_one :category
|
||||
belongs_to :parent_category, :class_name => 'Category'
|
||||
has_many :taggings
|
||||
has_many :tags, :through => :taggings
|
||||
has_many :any_tags, :through => :taggings, :class_name => 'Tag', :source => :tag, :with_deleted => true
|
||||
end
|
||||
|
||||
class Category < ActiveRecord::Base
|
||||
@@ -22,15 +25,47 @@ class Category < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
class Tag < ActiveRecord::Base
|
||||
has_many :taggings
|
||||
has_many :widgets, :through => :taggings
|
||||
end
|
||||
|
||||
class Tagging < ActiveRecord::Base
|
||||
belongs_to :tag
|
||||
belongs_to :widget
|
||||
acts_as_paranoid
|
||||
end
|
||||
|
||||
class NonParanoidAndroid < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class ParanoidTest < Test::Unit::TestCase
|
||||
fixtures :widgets, :categories, :categories_widgets
|
||||
fixtures :widgets, :categories, :categories_widgets, :tags, :taggings
|
||||
|
||||
def test_should_recognize_with_deleted_option
|
||||
assert_equal [1, 2], Widget.find(:all, :with_deleted => true).collect { |w| w.id }
|
||||
assert_equal [1], Widget.find(:all, :with_deleted => false).collect { |w| w.id }
|
||||
end
|
||||
|
||||
def test_should_recognize_only_deleted_option
|
||||
assert_equal [2], Widget.find(:all, :only_deleted => true).collect { |w| w.id }
|
||||
assert_equal [1], Widget.find(:all, :only_deleted => false).collect { |w| w.id }
|
||||
end
|
||||
|
||||
def test_should_exists_with_deleted
|
||||
assert Widget.exists_with_deleted?(2)
|
||||
assert !Widget.exists?(2)
|
||||
end
|
||||
|
||||
def test_should_exists_only_deleted
|
||||
assert Widget.exists_only_deleted?(2)
|
||||
assert !Widget.exists_only_deleted?(1)
|
||||
end
|
||||
|
||||
def test_should_count_with_deleted
|
||||
assert_equal 1, Widget.count
|
||||
assert_equal 2, Widget.count_with_deleted
|
||||
assert_equal 1, Widget.count_only_deleted
|
||||
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
|
||||
end
|
||||
|
||||
@@ -50,6 +85,7 @@ class ParanoidTest < Test::Unit::TestCase
|
||||
widgets(:widget_1).destroy!
|
||||
assert_equal 0, Widget.count
|
||||
assert_equal 0, Category.count
|
||||
assert_equal 1, Widget.count_only_deleted
|
||||
assert_equal 1, Widget.calculate_with_deleted(:count, :all)
|
||||
# Category doesn't get destroyed because the dependent before_destroy callback uses #destroy
|
||||
assert_equal 4, Category.calculate_with_deleted(:count, :all)
|
||||
@@ -94,6 +130,12 @@ class ParanoidTest < Test::Unit::TestCase
|
||||
assert_equal 1, Widget.count
|
||||
assert_equal 1, Widget.count(:all, :conditions => ['title=?', 'widget 1'])
|
||||
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
|
||||
assert_equal 1, Widget.count_only_deleted
|
||||
end
|
||||
|
||||
def test_should_find_only_deleted
|
||||
assert_equal [2], Widget.find_only_deleted(:all).collect { |w| w.id }
|
||||
assert_equal [1, 2], Widget.find_with_deleted(:all, :order => 'id').collect { |w| w.id }
|
||||
end
|
||||
|
||||
def test_should_not_find_deleted
|
||||
@@ -111,6 +153,16 @@ class ParanoidTest < Test::Unit::TestCase
|
||||
assert_equal [categories(:category_1)], widgets(:widget_1).habtm_categories
|
||||
end
|
||||
|
||||
def test_should_not_find_deleted_has_many_through_associations
|
||||
assert_equal 1, widgets(:widget_1).tags.size
|
||||
assert_equal [tags(:tag_2)], widgets(:widget_1).tags
|
||||
end
|
||||
|
||||
def test_should_find_has_many_through_associations_with_deleted
|
||||
assert_equal 2, widgets(:widget_1).any_tags.size
|
||||
assert_equal Tag.find(:all), widgets(:widget_1).any_tags
|
||||
end
|
||||
|
||||
def test_should_not_find_deleted_belongs_to_associations
|
||||
assert_nil Category.find_with_deleted(3).widget
|
||||
end
|
||||
@@ -208,6 +260,24 @@ class ParanoidTest < Test::Unit::TestCase
|
||||
assert_equal [], w[2].categories.search('c').ids
|
||||
assert_equal [3,4], w[2].categories.search_with_deleted('c').ids
|
||||
end
|
||||
|
||||
def test_should_recover_record
|
||||
Widget.find(1).destroy
|
||||
assert_equal true, Widget.find_with_deleted(1).deleted?
|
||||
|
||||
Widget.find_with_deleted(1).recover!
|
||||
assert_equal false, Widget.find(1).deleted?
|
||||
end
|
||||
|
||||
def test_should_recover_record_and_has_many_associations
|
||||
Widget.find(1).destroy
|
||||
assert_equal true, Widget.find_with_deleted(1).deleted?
|
||||
assert_equal true, Category.find_with_deleted(1).deleted?
|
||||
|
||||
Widget.find_with_deleted(1).recover_with_associations!(:categories)
|
||||
assert_equal false, Widget.find(1).deleted?
|
||||
assert_equal false, Category.find(1).deleted?
|
||||
end
|
||||
end
|
||||
|
||||
class Array
|
||||
|
||||
10
vendor/plugins/acts_as_paranoid/test/schema.rb
vendored
10
vendor/plugins/acts_as_paranoid/test/schema.rb
vendored
@@ -16,5 +16,15 @@ ActiveRecord::Schema.define(:version => 1) do
|
||||
t.column :category_id, :integer
|
||||
t.column :widget_id, :integer
|
||||
end
|
||||
|
||||
create_table :tags, :force => true do |t|
|
||||
t.column :name, :string, :limit => 50
|
||||
end
|
||||
|
||||
create_table :taggings, :force => true do |t|
|
||||
t.column :tag_id, :integer
|
||||
t.column :widget_id, :integer
|
||||
t.column :deleted_at, :timestamp
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,13 +1,27 @@
|
||||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
require 'test/unit'
|
||||
require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
|
||||
require 'rubygems'
|
||||
if ENV['RAILS'].nil?
|
||||
require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
|
||||
else
|
||||
# specific rails version targeted
|
||||
# load activerecord and plugin manually
|
||||
gem 'activerecord', "=#{ENV['RAILS']}"
|
||||
require 'active_record'
|
||||
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
||||
Dir["#{$LOAD_PATH.last}/**/*.rb"].each do |path|
|
||||
require path[$LOAD_PATH.last.size + 1..-1]
|
||||
end
|
||||
require File.join(File.dirname(__FILE__), '..', 'init.rb')
|
||||
end
|
||||
require 'active_record/fixtures'
|
||||
|
||||
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
||||
# do this so fixtures will load
|
||||
ActiveRecord::Base.configurations.update config
|
||||
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
||||
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
|
||||
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3'])
|
||||
|
||||
load(File.dirname(__FILE__) + "/schema.rb")
|
||||
|
||||
|
||||
82
vendor/plugins/acts_as_versioned/CHANGELOG
vendored
Normal file
82
vendor/plugins/acts_as_versioned/CHANGELOG
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
*GIT* (version numbers are overrated)
|
||||
|
||||
* (16 Jun 2008) Backwards Compatibility is overrated (big updates for rails 2.1)
|
||||
|
||||
* Use ActiveRecord 2.1's dirty attribute checking instead [Asa Calow]
|
||||
* Remove last traces of #non_versioned_fields
|
||||
* Remove AR::Base.find_version and AR::Base.find_versions, rely on AR association proxies and named_scope
|
||||
* Remove #versions_count, rely on AR association counter caching.
|
||||
* Remove #versioned_attributes, basically the same as AR::Base.versioned_columns
|
||||
|
||||
* (5 Oct 2006) Allow customization of #versions association options [Dan Peterson]
|
||||
|
||||
*0.5.1*
|
||||
|
||||
* (8 Aug 2006) Versioned models now belong to the unversioned model. @article_version.article.class => Article [Aslak Hellesoy]
|
||||
|
||||
*0.5* # do versions even matter for plugins?
|
||||
|
||||
* (21 Apr 2006) Added without_locking and without_revision methods.
|
||||
|
||||
Foo.without_revision do
|
||||
@foo.update_attributes ...
|
||||
end
|
||||
|
||||
*0.4*
|
||||
|
||||
* (28 March 2006) Rename non_versioned_fields to non_versioned_columns (old one is kept for compatibility).
|
||||
* (28 March 2006) Made explicit documentation note that string column names are required for non_versioned_columns.
|
||||
|
||||
*0.3.1*
|
||||
|
||||
* (7 Jan 2006) explicitly set :foreign_key option for the versioned model's belongs_to assocation for STI [Caged]
|
||||
* (7 Jan 2006) added tests to prove has_many :through joins work
|
||||
|
||||
*0.3*
|
||||
|
||||
* (2 Jan 2006) added ability to share a mixin with versioned class
|
||||
* (2 Jan 2006) changed the dynamic version model to MyModel::Version
|
||||
|
||||
*0.2.4*
|
||||
|
||||
* (27 Nov 2005) added note about possible destructive behavior of if_changed? [Michael Schuerig]
|
||||
|
||||
*0.2.3*
|
||||
|
||||
* (12 Nov 2005) fixed bug with old behavior of #blank? [Michael Schuerig]
|
||||
* (12 Nov 2005) updated tests to use ActiveRecord Schema
|
||||
|
||||
*0.2.2*
|
||||
|
||||
* (3 Nov 2005) added documentation note to #acts_as_versioned [Martin Jul]
|
||||
|
||||
*0.2.1*
|
||||
|
||||
* (6 Oct 2005) renamed dirty? to changed? to keep it uniform. it was aliased to keep it backwards compatible.
|
||||
|
||||
*0.2*
|
||||
|
||||
* (6 Oct 2005) added find_versions and find_version class methods.
|
||||
|
||||
* (6 Oct 2005) removed transaction from create_versioned_table().
|
||||
this way you can specify your own transaction around a group of operations.
|
||||
|
||||
* (30 Sep 2005) fixed bug where find_versions() would order by 'version' twice. (found by Joe Clark)
|
||||
|
||||
* (26 Sep 2005) added :sequence_name option to acts_as_versioned to set the sequence name on the versioned model
|
||||
|
||||
*0.1.3* (18 Sep 2005)
|
||||
|
||||
* First RubyForge release
|
||||
|
||||
*0.1.2*
|
||||
|
||||
* check if module is already included when acts_as_versioned is called
|
||||
|
||||
*0.1.1*
|
||||
|
||||
* Adding tests and rdocs
|
||||
|
||||
*0.1*
|
||||
|
||||
* Initial transfer from Rails ticket: http://dev.rubyonrails.com/ticket/1974
|
||||
20
vendor/plugins/acts_as_versioned/MIT-LICENSE
vendored
Normal file
20
vendor/plugins/acts_as_versioned/MIT-LICENSE
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2005 Rick Olson
|
||||
|
||||
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.
|
||||
28
vendor/plugins/acts_as_versioned/README
vendored
Normal file
28
vendor/plugins/acts_as_versioned/README
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
= acts_as_versioned
|
||||
|
||||
This library adds simple versioning to an ActiveRecord module. ActiveRecord is required.
|
||||
|
||||
== Resources
|
||||
|
||||
Install
|
||||
|
||||
* gem install acts_as_versioned
|
||||
|
||||
Rubyforge project
|
||||
|
||||
* http://rubyforge.org/projects/ar-versioned
|
||||
|
||||
RDocs
|
||||
|
||||
* http://ar-versioned.rubyforge.org
|
||||
|
||||
Subversion
|
||||
|
||||
* http://techno-weenie.net/svn/projects/acts_as_versioned
|
||||
|
||||
Collaboa
|
||||
|
||||
* http://collaboa.techno-weenie.net/repository/browse/acts_as_versioned
|
||||
|
||||
Special thanks to Dreamer on ##rubyonrails for help in early testing. His ServerSideWiki (http://serversidewiki.com)
|
||||
was the first project to use acts_as_versioned <em>in the wild</em>.
|
||||
41
vendor/plugins/acts_as_versioned/RUNNING_UNIT_TESTS
vendored
Normal file
41
vendor/plugins/acts_as_versioned/RUNNING_UNIT_TESTS
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
== Creating the test database
|
||||
|
||||
The default name for the test databases is "activerecord_versioned". If you
|
||||
want to use another database name then be sure to update the connection
|
||||
adapter setups you want to test with in test/connections/<your database>/connection.rb.
|
||||
When you have the database online, you can import the fixture tables with
|
||||
the test/fixtures/db_definitions/*.sql files.
|
||||
|
||||
Make sure that you create database objects with the same user that you specified in i
|
||||
connection.rb otherwise (on Postgres, at least) tests for default values will fail.
|
||||
|
||||
== Running with Rake
|
||||
|
||||
The easiest way to run the unit tests is through Rake. The default task runs
|
||||
the entire test suite for all the adapters. You can also run the suite on just
|
||||
one adapter by using the tasks test_mysql_ruby, test_ruby_mysql, test_sqlite,
|
||||
or test_postresql. For more information, checkout the full array of rake tasks with "rake -T"
|
||||
|
||||
Rake can be found at http://rake.rubyforge.org
|
||||
|
||||
== Running by hand
|
||||
|
||||
Unit tests are located in test directory. If you only want to run a single test suite,
|
||||
or don't want to bother with Rake, you can do so with something like:
|
||||
|
||||
cd test; ruby -I "connections/native_mysql" base_test.rb
|
||||
|
||||
That'll run the base suite using the MySQL-Ruby adapter. Change the adapter
|
||||
and test suite name as needed.
|
||||
|
||||
== Faster tests
|
||||
|
||||
If you are using a database that supports transactions, you can set the
|
||||
"AR_TX_FIXTURES" environment variable to "yes" to use transactional fixtures.
|
||||
This gives a very large speed boost. With rake:
|
||||
|
||||
rake AR_TX_FIXTURES=yes
|
||||
|
||||
Or, by hand:
|
||||
|
||||
AR_TX_FIXTURES=yes ruby -I connections/native_sqlite3 base_test.rb
|
||||
180
vendor/plugins/acts_as_versioned/Rakefile
vendored
Normal file
180
vendor/plugins/acts_as_versioned/Rakefile
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
require 'rubygems'
|
||||
|
||||
require 'rake/rdoctask'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/testtask'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
|
||||
PKG_NAME = 'acts_as_versioned'
|
||||
PKG_VERSION = '0.3.1'
|
||||
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
||||
PROD_HOST = "technoweenie@bidwell.textdrive.com"
|
||||
RUBY_FORGE_PROJECT = 'ar-versioned'
|
||||
RUBY_FORGE_USER = 'technoweenie'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the calculations plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the calculations plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = "#{PKG_NAME} -- Simple versioning with active record models"
|
||||
rdoc.options << '--line-numbers --inline-source'
|
||||
rdoc.rdoc_files.include('README', 'CHANGELOG', 'RUNNING_UNIT_TESTS')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
|
||||
spec = Gem::Specification.new do |s|
|
||||
s.name = PKG_NAME
|
||||
s.version = PKG_VERSION
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.summary = "Simple versioning with active record models"
|
||||
s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG RUNNING_UNIT_TESTS)
|
||||
s.files.delete "acts_as_versioned_plugin.sqlite.db"
|
||||
s.files.delete "acts_as_versioned_plugin.sqlite3.db"
|
||||
s.files.delete "test/debug.log"
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'acts_as_versioned'
|
||||
s.has_rdoc = true
|
||||
s.test_files = Dir['test/**/*_test.rb']
|
||||
s.add_dependency 'activerecord', '>= 1.10.1'
|
||||
s.add_dependency 'activesupport', '>= 1.1.1'
|
||||
s.author = "Rick Olson"
|
||||
s.email = "technoweenie@gmail.com"
|
||||
s.homepage = "http://techno-weenie.net"
|
||||
end
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |pkg|
|
||||
pkg.need_tar = true
|
||||
end
|
||||
|
||||
desc "Publish the API documentation"
|
||||
task :pdoc => [:rdoc] do
|
||||
Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload
|
||||
end
|
||||
|
||||
desc 'Publish the gem and API docs'
|
||||
task :publish => [:pdoc, :rubyforge_upload]
|
||||
|
||||
desc "Publish the release files to RubyForge."
|
||||
task :rubyforge_upload => :package do
|
||||
files = %w(gem tgz).map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
|
||||
|
||||
if RUBY_FORGE_PROJECT then
|
||||
require 'net/http'
|
||||
require 'open-uri'
|
||||
|
||||
project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
|
||||
project_data = open(project_uri) { |data| data.read }
|
||||
group_id = project_data[/[?&]group_id=(\d+)/, 1]
|
||||
raise "Couldn't get group id" unless group_id
|
||||
|
||||
# This echos password to shell which is a bit sucky
|
||||
if ENV["RUBY_FORGE_PASSWORD"]
|
||||
password = ENV["RUBY_FORGE_PASSWORD"]
|
||||
else
|
||||
print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
|
||||
password = STDIN.gets.chomp
|
||||
end
|
||||
|
||||
login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
||||
data = [
|
||||
"login=1",
|
||||
"form_loginname=#{RUBY_FORGE_USER}",
|
||||
"form_pw=#{password}"
|
||||
].join("&")
|
||||
http.post("/account/login.php", data)
|
||||
end
|
||||
|
||||
cookie = login_response["set-cookie"]
|
||||
raise "Login failed" unless cookie
|
||||
headers = { "Cookie" => cookie }
|
||||
|
||||
release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
|
||||
release_data = open(release_uri, headers) { |data| data.read }
|
||||
package_id = release_data[/[?&]package_id=(\d+)/, 1]
|
||||
raise "Couldn't get package id" unless package_id
|
||||
|
||||
first_file = true
|
||||
release_id = ""
|
||||
|
||||
files.each do |filename|
|
||||
basename = File.basename(filename)
|
||||
file_ext = File.extname(filename)
|
||||
file_data = File.open(filename, "rb") { |file| file.read }
|
||||
|
||||
puts "Releasing #{basename}..."
|
||||
|
||||
release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
||||
release_date = Time.now.strftime("%Y-%m-%d %H:%M")
|
||||
type_map = {
|
||||
".zip" => "3000",
|
||||
".tgz" => "3110",
|
||||
".gz" => "3110",
|
||||
".gem" => "1400"
|
||||
}; type_map.default = "9999"
|
||||
type = type_map[file_ext]
|
||||
boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
|
||||
|
||||
query_hash = if first_file then
|
||||
{
|
||||
"group_id" => group_id,
|
||||
"package_id" => package_id,
|
||||
"release_name" => PKG_FILE_NAME,
|
||||
"release_date" => release_date,
|
||||
"type_id" => type,
|
||||
"processor_id" => "8000", # Any
|
||||
"release_notes" => "",
|
||||
"release_changes" => "",
|
||||
"preformatted" => "1",
|
||||
"submit" => "1"
|
||||
}
|
||||
else
|
||||
{
|
||||
"group_id" => group_id,
|
||||
"release_id" => release_id,
|
||||
"package_id" => package_id,
|
||||
"step2" => "1",
|
||||
"type_id" => type,
|
||||
"processor_id" => "8000", # Any
|
||||
"submit" => "Add This File"
|
||||
}
|
||||
end
|
||||
|
||||
query = "?" + query_hash.map do |(name, value)|
|
||||
[name, URI.encode(value)].join("=")
|
||||
end.join("&")
|
||||
|
||||
data = [
|
||||
"--" + boundary,
|
||||
"Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
|
||||
"Content-Type: application/octet-stream",
|
||||
"Content-Transfer-Encoding: binary",
|
||||
"", file_data, ""
|
||||
].join("\x0D\x0A")
|
||||
|
||||
release_headers = headers.merge(
|
||||
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
|
||||
)
|
||||
|
||||
target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
|
||||
http.post(target + query, data, release_headers)
|
||||
end
|
||||
|
||||
if first_file then
|
||||
release_id = release_response.body[/release_id=(\d+)/, 1]
|
||||
raise("Couldn't get release id") unless release_id
|
||||
end
|
||||
|
||||
first_file = false
|
||||
end
|
||||
end
|
||||
end
|
||||
4
vendor/plugins/acts_as_versioned/VERSION.yml
vendored
Normal file
4
vendor/plugins/acts_as_versioned/VERSION.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
:patch: 2
|
||||
:major: 0
|
||||
:minor: 5
|
||||
29
vendor/plugins/acts_as_versioned/acts_as_versioned.gemspec
vendored
Normal file
29
vendor/plugins/acts_as_versioned/acts_as_versioned.gemspec
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{acts_as_versioned}
|
||||
s.version = "0.5.2"
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["technoweenie"]
|
||||
s.date = %q{2009-01-20}
|
||||
s.description = %q{TODO}
|
||||
s.email = %q{technoweenie@bidwell.textdrive.com}
|
||||
s.files = ["VERSION.yml", "lib/acts_as_versioned.rb", "test/abstract_unit.rb", "test/database.yml", "test/fixtures", "test/fixtures/authors.yml", "test/fixtures/landmark.rb", "test/fixtures/landmark_versions.yml", "test/fixtures/landmarks.yml", "test/fixtures/locked_pages.yml", "test/fixtures/locked_pages_revisions.yml", "test/fixtures/migrations", "test/fixtures/migrations/1_add_versioned_tables.rb", "test/fixtures/page.rb", "test/fixtures/page_versions.yml", "test/fixtures/pages.yml", "test/fixtures/widget.rb", "test/migration_test.rb", "test/schema.rb", "test/versioned_test.rb"]
|
||||
s.has_rdoc = true
|
||||
s.homepage = %q{http://github.com/technoweenie/acts_as_versioned}
|
||||
s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
|
||||
s.require_paths = ["lib"]
|
||||
s.rubygems_version = %q{1.3.1}
|
||||
s.summary = %q{TODO}
|
||||
|
||||
if s.respond_to? :specification_version then
|
||||
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
||||
s.specification_version = 2
|
||||
|
||||
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
||||
else
|
||||
end
|
||||
else
|
||||
end
|
||||
end
|
||||
1
vendor/plugins/acts_as_versioned/init.rb
vendored
Normal file
1
vendor/plugins/acts_as_versioned/init.rb
vendored
Normal file
@@ -0,0 +1 @@
|
||||
require 'acts_as_versioned'
|
||||
486
vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb
vendored
Normal file
486
vendor/plugins/acts_as_versioned/lib/acts_as_versioned.rb
vendored
Normal file
@@ -0,0 +1,486 @@
|
||||
# Copyright (c) 2005 Rick Olson
|
||||
#
|
||||
# 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.
|
||||
|
||||
module ActiveRecord #:nodoc:
|
||||
module Acts #:nodoc:
|
||||
# Specify this act if you want to save a copy of the row in a versioned table. This assumes there is a
|
||||
# versioned table ready and that your model has a version field. This works with optimistic locking if the lock_version
|
||||
# column is present as well.
|
||||
#
|
||||
# The class for the versioned model is derived the first time it is seen. Therefore, if you change your database schema you have to restart
|
||||
# your container for the changes to be reflected. In development mode this usually means restarting WEBrick.
|
||||
#
|
||||
# class Page < ActiveRecord::Base
|
||||
# # assumes pages_versions table
|
||||
# acts_as_versioned
|
||||
# end
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# page = Page.create(:title => 'hello world!')
|
||||
# page.version # => 1
|
||||
#
|
||||
# page.title = 'hello world'
|
||||
# page.save
|
||||
# page.version # => 2
|
||||
# page.versions.size # => 2
|
||||
#
|
||||
# page.revert_to(1) # using version number
|
||||
# page.title # => 'hello world!'
|
||||
#
|
||||
# page.revert_to(page.versions.last) # using versioned instance
|
||||
# page.title # => 'hello world'
|
||||
#
|
||||
# page.versions.earliest # efficient query to find the first version
|
||||
# page.versions.latest # efficient query to find the most recently created version
|
||||
#
|
||||
#
|
||||
# Simple Queries to page between versions
|
||||
#
|
||||
# page.versions.before(version)
|
||||
# page.versions.after(version)
|
||||
#
|
||||
# Access the previous/next versions from the versioned model itself
|
||||
#
|
||||
# version = page.versions.latest
|
||||
# version.previous # go back one version
|
||||
# version.next # go forward one version
|
||||
#
|
||||
# See ActiveRecord::Acts::Versioned::ClassMethods#acts_as_versioned for configuration options
|
||||
module Versioned
|
||||
CALLBACKS = [:set_new_version, :save_version, :save_version?]
|
||||
def self.included(base) # :nodoc:
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# == Configuration options
|
||||
#
|
||||
# * <tt>class_name</tt> - versioned model class name (default: PageVersion in the above example)
|
||||
# * <tt>table_name</tt> - versioned model table name (default: page_versions in the above example)
|
||||
# * <tt>foreign_key</tt> - foreign key used to relate the versioned model to the original model (default: page_id in the above example)
|
||||
# * <tt>inheritance_column</tt> - name of the column to save the model's inheritance_column value for STI. (default: versioned_type)
|
||||
# * <tt>version_column</tt> - name of the column in the model that keeps the version number (default: version)
|
||||
# * <tt>sequence_name</tt> - name of the custom sequence to be used by the versioned model.
|
||||
# * <tt>limit</tt> - number of revisions to keep, defaults to unlimited
|
||||
# * <tt>if</tt> - symbol of method to check before saving a new version. If this method returns false, a new version is not saved.
|
||||
# For finer control, pass either a Proc or modify Model#version_condition_met?
|
||||
#
|
||||
# acts_as_versioned :if => Proc.new { |auction| !auction.expired? }
|
||||
#
|
||||
# or...
|
||||
#
|
||||
# class Auction
|
||||
# def version_condition_met? # totally bypasses the <tt>:if</tt> option
|
||||
# !expired?
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# * <tt>if_changed</tt> - Simple way of specifying attributes that are required to be changed before saving a model. This takes
|
||||
# either a symbol or array of symbols.
|
||||
#
|
||||
# * <tt>extend</tt> - Lets you specify a module to be mixed in both the original and versioned models. You can also just pass a block
|
||||
# to create an anonymous mixin:
|
||||
#
|
||||
# class Auction
|
||||
# acts_as_versioned do
|
||||
# def started?
|
||||
# !started_at.nil?
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# or...
|
||||
#
|
||||
# module AuctionExtension
|
||||
# def started?
|
||||
# !started_at.nil?
|
||||
# end
|
||||
# end
|
||||
# class Auction
|
||||
# acts_as_versioned :extend => AuctionExtension
|
||||
# end
|
||||
#
|
||||
# Example code:
|
||||
#
|
||||
# @auction = Auction.find(1)
|
||||
# @auction.started?
|
||||
# @auction.versions.first.started?
|
||||
#
|
||||
# == Database Schema
|
||||
#
|
||||
# The model that you're versioning needs to have a 'version' attribute. The model is versioned
|
||||
# into a table called #{model}_versions where the model name is singlular. The _versions table should
|
||||
# contain all the fields you want versioned, the same version column, and a #{model}_id foreign key field.
|
||||
#
|
||||
# A lock_version field is also accepted if your model uses Optimistic Locking. If your table uses Single Table inheritance,
|
||||
# then that field is reflected in the versioned model as 'versioned_type' by default.
|
||||
#
|
||||
# Acts_as_versioned comes prepared with the ActiveRecord::Acts::Versioned::ActMethods::ClassMethods#create_versioned_table
|
||||
# method, perfect for a migration. It will also create the version column if the main model does not already have it.
|
||||
#
|
||||
# class AddVersions < ActiveRecord::Migration
|
||||
# def self.up
|
||||
# # create_versioned_table takes the same options hash
|
||||
# # that create_table does
|
||||
# Post.create_versioned_table
|
||||
# end
|
||||
#
|
||||
# def self.down
|
||||
# Post.drop_versioned_table
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# == Changing What Fields Are Versioned
|
||||
#
|
||||
# By default, acts_as_versioned will version all but these fields:
|
||||
#
|
||||
# [self.primary_key, inheritance_column, 'version', 'lock_version', versioned_inheritance_column]
|
||||
#
|
||||
# You can add or change those by modifying #non_versioned_columns. Note that this takes strings and not symbols.
|
||||
#
|
||||
# class Post < ActiveRecord::Base
|
||||
# acts_as_versioned
|
||||
# self.non_versioned_columns << 'comments_count'
|
||||
# end
|
||||
#
|
||||
def acts_as_versioned(options = {}, &extension)
|
||||
# don't allow multiple calls
|
||||
return if self.included_modules.include?(ActiveRecord::Acts::Versioned::ActMethods)
|
||||
|
||||
send :include, ActiveRecord::Acts::Versioned::ActMethods
|
||||
|
||||
cattr_accessor :versioned_class_name, :versioned_foreign_key, :versioned_table_name, :versioned_inheritance_column,
|
||||
:version_column, :max_version_limit, :track_altered_attributes, :version_condition, :version_sequence_name, :non_versioned_columns,
|
||||
:version_association_options, :version_if_changed
|
||||
|
||||
self.versioned_class_name = options[:class_name] || "Version"
|
||||
self.versioned_foreign_key = options[:foreign_key] || self.to_s.foreign_key
|
||||
self.versioned_table_name = options[:table_name] || "#{table_name_prefix}#{base_class.name.demodulize.underscore}_versions#{table_name_suffix}"
|
||||
self.versioned_inheritance_column = options[:inheritance_column] || "versioned_#{inheritance_column}"
|
||||
self.version_column = options[:version_column] || 'version'
|
||||
self.version_sequence_name = options[:sequence_name]
|
||||
self.max_version_limit = options[:limit].to_i
|
||||
self.version_condition = options[:if] || true
|
||||
self.non_versioned_columns = [self.primary_key, inheritance_column, self.version_column, 'lock_version', versioned_inheritance_column] + options[:non_versioned_columns].to_a.map(&:to_s)
|
||||
self.version_association_options = {
|
||||
:class_name => "#{self.to_s}::#{versioned_class_name}",
|
||||
:foreign_key => versioned_foreign_key,
|
||||
:dependent => :delete_all
|
||||
}.merge(options[:association_options] || {})
|
||||
|
||||
if block_given?
|
||||
extension_module_name = "#{versioned_class_name}Extension"
|
||||
silence_warnings do
|
||||
self.const_set(extension_module_name, Module.new(&extension))
|
||||
end
|
||||
|
||||
options[:extend] = self.const_get(extension_module_name)
|
||||
end
|
||||
|
||||
class_eval <<-CLASS_METHODS
|
||||
has_many :versions, version_association_options do
|
||||
# finds earliest version of this record
|
||||
def earliest
|
||||
@earliest ||= find(:first, :order => '#{version_column}')
|
||||
end
|
||||
|
||||
# find latest version of this record
|
||||
def latest
|
||||
@latest ||= find(:first, :order => '#{version_column} desc')
|
||||
end
|
||||
end
|
||||
before_save :set_new_version
|
||||
after_save :save_version
|
||||
after_save :clear_old_versions
|
||||
|
||||
unless options[:if_changed].nil?
|
||||
self.track_altered_attributes = true
|
||||
options[:if_changed] = [options[:if_changed]] unless options[:if_changed].is_a?(Array)
|
||||
self.version_if_changed = options[:if_changed].map(&:to_s)
|
||||
end
|
||||
|
||||
include options[:extend] if options[:extend].is_a?(Module)
|
||||
CLASS_METHODS
|
||||
|
||||
# create the dynamic versioned model
|
||||
const_set(versioned_class_name, Class.new(ActiveRecord::Base)).class_eval do
|
||||
def self.reloadable? ; false ; end
|
||||
# find first version before the given version
|
||||
def self.before(version)
|
||||
find :first, :order => 'version desc',
|
||||
:conditions => ["#{original_class.versioned_foreign_key} = ? and version < ?", version.send(original_class.versioned_foreign_key), version.version]
|
||||
end
|
||||
|
||||
# find first version after the given version.
|
||||
def self.after(version)
|
||||
find :first, :order => 'version',
|
||||
:conditions => ["#{original_class.versioned_foreign_key} = ? and version > ?", version.send(original_class.versioned_foreign_key), version.version]
|
||||
end
|
||||
|
||||
def previous
|
||||
self.class.before(self)
|
||||
end
|
||||
|
||||
def next
|
||||
self.class.after(self)
|
||||
end
|
||||
|
||||
def versions_count
|
||||
page.version
|
||||
end
|
||||
end
|
||||
|
||||
versioned_class.cattr_accessor :original_class
|
||||
versioned_class.original_class = self
|
||||
versioned_class.set_table_name versioned_table_name
|
||||
versioned_class.belongs_to self.to_s.demodulize.underscore.to_sym,
|
||||
:class_name => "::#{self.to_s}",
|
||||
:foreign_key => versioned_foreign_key
|
||||
versioned_class.send :include, options[:extend] if options[:extend].is_a?(Module)
|
||||
versioned_class.set_sequence_name version_sequence_name if version_sequence_name
|
||||
end
|
||||
end
|
||||
|
||||
module ActMethods
|
||||
def self.included(base) # :nodoc:
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
# Saves a version of the model in the versioned table. This is called in the after_save callback by default
|
||||
def save_version
|
||||
if @saving_version
|
||||
@saving_version = nil
|
||||
rev = self.class.versioned_class.new
|
||||
clone_versioned_model(self, rev)
|
||||
rev.send("#{self.class.version_column}=", send(self.class.version_column))
|
||||
rev.send("#{self.class.versioned_foreign_key}=", id)
|
||||
rev.save
|
||||
end
|
||||
end
|
||||
|
||||
# Clears old revisions if a limit is set with the :limit option in <tt>acts_as_versioned</tt>.
|
||||
# Override this method to set your own criteria for clearing old versions.
|
||||
def clear_old_versions
|
||||
return if self.class.max_version_limit == 0
|
||||
excess_baggage = send(self.class.version_column).to_i - self.class.max_version_limit
|
||||
if excess_baggage > 0
|
||||
self.class.versioned_class.delete_all ["#{self.class.version_column} <= ? and #{self.class.versioned_foreign_key} = ?", excess_baggage, id]
|
||||
end
|
||||
end
|
||||
|
||||
# Reverts a model to a given version. Takes either a version number or an instance of the versioned model
|
||||
def revert_to(version)
|
||||
if version.is_a?(self.class.versioned_class)
|
||||
return false unless version.send(self.class.versioned_foreign_key) == id and !version.new_record?
|
||||
else
|
||||
return false unless version = versions.send("find_by_#{self.class.version_column}", version)
|
||||
end
|
||||
self.clone_versioned_model(version, self)
|
||||
send("#{self.class.version_column}=", version.send(self.class.version_column))
|
||||
true
|
||||
end
|
||||
|
||||
# Reverts a model to a given version and saves the model.
|
||||
# Takes either a version number or an instance of the versioned model
|
||||
def revert_to!(version)
|
||||
revert_to(version) ? save_without_revision : false
|
||||
end
|
||||
|
||||
# Temporarily turns off Optimistic Locking while saving. Used when reverting so that a new version is not created.
|
||||
def save_without_revision
|
||||
save_without_revision!
|
||||
true
|
||||
rescue
|
||||
false
|
||||
end
|
||||
|
||||
def save_without_revision!
|
||||
without_locking do
|
||||
without_revision do
|
||||
save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def altered?
|
||||
track_altered_attributes ? (version_if_changed - changed).length < version_if_changed.length : changed?
|
||||
end
|
||||
|
||||
# Clones a model. Used when saving a new version or reverting a model's version.
|
||||
def clone_versioned_model(orig_model, new_model)
|
||||
self.class.versioned_columns.each do |col|
|
||||
new_model.send("#{col.name}=", orig_model.send(col.name)) if orig_model.has_attribute?(col.name)
|
||||
end
|
||||
|
||||
if orig_model.is_a?(self.class.versioned_class)
|
||||
new_model[new_model.class.inheritance_column] = orig_model[self.class.versioned_inheritance_column]
|
||||
elsif new_model.is_a?(self.class.versioned_class)
|
||||
new_model[self.class.versioned_inheritance_column] = orig_model[orig_model.class.inheritance_column]
|
||||
end
|
||||
end
|
||||
|
||||
# Checks whether a new version shall be saved or not. Calls <tt>version_condition_met?</tt> and <tt>changed?</tt>.
|
||||
def save_version?
|
||||
version_condition_met? && altered?
|
||||
end
|
||||
|
||||
# Checks condition set in the :if option to check whether a revision should be created or not. Override this for
|
||||
# custom version condition checking.
|
||||
def version_condition_met?
|
||||
case
|
||||
when version_condition.is_a?(Symbol)
|
||||
send(version_condition)
|
||||
when version_condition.respond_to?(:call) && (version_condition.arity == 1 || version_condition.arity == -1)
|
||||
version_condition.call(self)
|
||||
else
|
||||
version_condition
|
||||
end
|
||||
end
|
||||
|
||||
# Executes the block with the versioning callbacks disabled.
|
||||
#
|
||||
# @foo.without_revision do
|
||||
# @foo.save
|
||||
# end
|
||||
#
|
||||
def without_revision(&block)
|
||||
self.class.without_revision(&block)
|
||||
end
|
||||
|
||||
# Turns off optimistic locking for the duration of the block
|
||||
#
|
||||
# @foo.without_locking do
|
||||
# @foo.save
|
||||
# end
|
||||
#
|
||||
def without_locking(&block)
|
||||
self.class.without_locking(&block)
|
||||
end
|
||||
|
||||
def empty_callback() end #:nodoc:
|
||||
|
||||
protected
|
||||
# sets the new version before saving, unless you're using optimistic locking. In that case, let it take care of the version.
|
||||
def set_new_version
|
||||
@saving_version = new_record? || save_version?
|
||||
self.send("#{self.class.version_column}=", next_version) if new_record? || (!locking_enabled? && save_version?)
|
||||
end
|
||||
|
||||
# Gets the next available version for the current record, or 1 for a new record
|
||||
def next_version
|
||||
(new_record? ? 0 : versions.calculate(:max, version_column).to_i) + 1
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Returns an array of columns that are versioned. See non_versioned_columns
|
||||
def versioned_columns
|
||||
@versioned_columns ||= columns.select { |c| !non_versioned_columns.include?(c.name) }
|
||||
end
|
||||
|
||||
# Returns an instance of the dynamic versioned model
|
||||
def versioned_class
|
||||
const_get versioned_class_name
|
||||
end
|
||||
|
||||
# Rake migration task to create the versioned table using options passed to acts_as_versioned
|
||||
def create_versioned_table(create_table_options = {})
|
||||
# create version column in main table if it does not exist
|
||||
if !self.content_columns.find { |c| [version_column.to_s, 'lock_version'].include? c.name }
|
||||
self.connection.add_column table_name, version_column, :integer
|
||||
self.reset_column_information
|
||||
end
|
||||
|
||||
return if connection.table_exists?(versioned_table_name)
|
||||
|
||||
self.connection.create_table(versioned_table_name, create_table_options) do |t|
|
||||
t.column versioned_foreign_key, :integer
|
||||
t.column version_column, :integer
|
||||
end
|
||||
|
||||
self.versioned_columns.each do |col|
|
||||
self.connection.add_column versioned_table_name, col.name, col.type,
|
||||
:limit => col.limit,
|
||||
:default => col.default,
|
||||
:scale => col.scale,
|
||||
:precision => col.precision
|
||||
end
|
||||
|
||||
if type_col = self.columns_hash[inheritance_column]
|
||||
self.connection.add_column versioned_table_name, versioned_inheritance_column, type_col.type,
|
||||
:limit => type_col.limit,
|
||||
:default => type_col.default,
|
||||
:scale => type_col.scale,
|
||||
:precision => type_col.precision
|
||||
end
|
||||
|
||||
self.connection.add_index versioned_table_name, versioned_foreign_key
|
||||
end
|
||||
|
||||
# Rake migration task to drop the versioned table
|
||||
def drop_versioned_table
|
||||
self.connection.drop_table versioned_table_name
|
||||
end
|
||||
|
||||
# Executes the block with the versioning callbacks disabled.
|
||||
#
|
||||
# Foo.without_revision do
|
||||
# @foo.save
|
||||
# end
|
||||
#
|
||||
def without_revision(&block)
|
||||
class_eval do
|
||||
CALLBACKS.each do |attr_name|
|
||||
alias_method "orig_#{attr_name}".to_sym, attr_name
|
||||
alias_method attr_name, :empty_callback
|
||||
end
|
||||
end
|
||||
block.call
|
||||
ensure
|
||||
class_eval do
|
||||
CALLBACKS.each do |attr_name|
|
||||
alias_method attr_name, "orig_#{attr_name}".to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Turns off optimistic locking for the duration of the block
|
||||
#
|
||||
# Foo.without_locking do
|
||||
# @foo.save
|
||||
# end
|
||||
#
|
||||
def without_locking(&block)
|
||||
current = ActiveRecord::Base.lock_optimistically
|
||||
ActiveRecord::Base.lock_optimistically = false if current
|
||||
begin
|
||||
block.call
|
||||
ensure
|
||||
ActiveRecord::Base.lock_optimistically = true if current
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Base.send :include, ActiveRecord::Acts::Versioned
|
||||
48
vendor/plugins/acts_as_versioned/test/abstract_unit.rb
vendored
Normal file
48
vendor/plugins/acts_as_versioned/test/abstract_unit.rb
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
$:.unshift(File.dirname(__FILE__) + '/../../../rails/activesupport/lib')
|
||||
$:.unshift(File.dirname(__FILE__) + '/../../../rails/activerecord/lib')
|
||||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
require 'test/unit'
|
||||
begin
|
||||
require 'active_support'
|
||||
require 'active_record'
|
||||
require 'active_record/fixtures'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
retry
|
||||
end
|
||||
|
||||
begin
|
||||
require 'ruby-debug'
|
||||
Debugger.start
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
require 'acts_as_versioned'
|
||||
|
||||
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
||||
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
||||
ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']}
|
||||
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
|
||||
|
||||
load(File.dirname(__FILE__) + "/schema.rb")
|
||||
|
||||
# set up custom sequence on widget_versions for DBs that support sequences
|
||||
if ENV['DB'] == 'postgresql'
|
||||
ActiveRecord::Base.connection.execute "DROP SEQUENCE widgets_seq;" rescue nil
|
||||
ActiveRecord::Base.connection.remove_column :widget_versions, :id
|
||||
ActiveRecord::Base.connection.execute "CREATE SEQUENCE widgets_seq START 101;"
|
||||
ActiveRecord::Base.connection.execute "ALTER TABLE widget_versions ADD COLUMN id INTEGER PRIMARY KEY DEFAULT nextval('widgets_seq');"
|
||||
end
|
||||
|
||||
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
|
||||
$:.unshift(Test::Unit::TestCase.fixture_path)
|
||||
|
||||
class Test::Unit::TestCase #:nodoc:
|
||||
# Turn off transactional fixtures if you're working with MyISAM tables in MySQL
|
||||
self.use_transactional_fixtures = true
|
||||
|
||||
# Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
|
||||
self.use_instantiated_fixtures = false
|
||||
|
||||
# Add more helper methods to be used by all tests here...
|
||||
end
|
||||
18
vendor/plugins/acts_as_versioned/test/database.yml
vendored
Normal file
18
vendor/plugins/acts_as_versioned/test/database.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
sqlite:
|
||||
:adapter: sqlite
|
||||
:dbfile: acts_as_versioned_plugin.sqlite.db
|
||||
sqlite3:
|
||||
:adapter: sqlite3
|
||||
:dbfile: acts_as_versioned_plugin.sqlite3.db
|
||||
postgresql:
|
||||
:adapter: postgresql
|
||||
:username: postgres
|
||||
:password: postgres
|
||||
:database: acts_as_versioned_plugin_test
|
||||
:min_messages: ERROR
|
||||
mysql:
|
||||
:adapter: mysql
|
||||
:host: localhost
|
||||
:username: rails
|
||||
:password:
|
||||
:database: acts_as_versioned_plugin_test
|
||||
6
vendor/plugins/acts_as_versioned/test/fixtures/authors.yml
vendored
Normal file
6
vendor/plugins/acts_as_versioned/test/fixtures/authors.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
caged:
|
||||
id: 1
|
||||
name: caged
|
||||
mly:
|
||||
id: 2
|
||||
name: mly
|
||||
3
vendor/plugins/acts_as_versioned/test/fixtures/landmark.rb
vendored
Normal file
3
vendor/plugins/acts_as_versioned/test/fixtures/landmark.rb
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
class Landmark < ActiveRecord::Base
|
||||
acts_as_versioned :if_changed => [ :name, :longitude, :latitude ]
|
||||
end
|
||||
7
vendor/plugins/acts_as_versioned/test/fixtures/landmark_versions.yml
vendored
Normal file
7
vendor/plugins/acts_as_versioned/test/fixtures/landmark_versions.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
washington:
|
||||
id: 1
|
||||
landmark_id: 1
|
||||
version: 1
|
||||
name: Washington, D.C.
|
||||
latitude: 38.895
|
||||
longitude: -77.036667
|
||||
7
vendor/plugins/acts_as_versioned/test/fixtures/landmarks.yml
vendored
Normal file
7
vendor/plugins/acts_as_versioned/test/fixtures/landmarks.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
washington:
|
||||
id: 1
|
||||
name: Washington, D.C.
|
||||
latitude: 38.895
|
||||
longitude: -77.036667
|
||||
doesnt_trigger_version: This is not important
|
||||
version: 1
|
||||
10
vendor/plugins/acts_as_versioned/test/fixtures/locked_pages.yml
vendored
Normal file
10
vendor/plugins/acts_as_versioned/test/fixtures/locked_pages.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
welcome:
|
||||
id: 1
|
||||
title: Welcome to the weblog
|
||||
lock_version: 24
|
||||
type: LockedPage
|
||||
thinking:
|
||||
id: 2
|
||||
title: So I was thinking
|
||||
lock_version: 24
|
||||
type: SpecialLockedPage
|
||||
27
vendor/plugins/acts_as_versioned/test/fixtures/locked_pages_revisions.yml
vendored
Normal file
27
vendor/plugins/acts_as_versioned/test/fixtures/locked_pages_revisions.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
welcome_1:
|
||||
id: 1
|
||||
page_id: 1
|
||||
title: Welcome to the weblg
|
||||
lock_version: 23
|
||||
version_type: LockedPage
|
||||
|
||||
welcome_2:
|
||||
id: 2
|
||||
page_id: 1
|
||||
title: Welcome to the weblog
|
||||
lock_version: 24
|
||||
version_type: LockedPage
|
||||
|
||||
thinking_1:
|
||||
id: 3
|
||||
page_id: 2
|
||||
title: So I was thinking!!!
|
||||
lock_version: 23
|
||||
version_type: SpecialLockedPage
|
||||
|
||||
thinking_2:
|
||||
id: 4
|
||||
page_id: 2
|
||||
title: So I was thinking
|
||||
lock_version: 24
|
||||
version_type: SpecialLockedPage
|
||||
15
vendor/plugins/acts_as_versioned/test/fixtures/migrations/1_add_versioned_tables.rb
vendored
Normal file
15
vendor/plugins/acts_as_versioned/test/fixtures/migrations/1_add_versioned_tables.rb
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
class AddVersionedTables < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table("things") do |t|
|
||||
t.column :title, :text
|
||||
t.column :price, :decimal, :precision => 7, :scale => 2
|
||||
t.column :type, :string
|
||||
end
|
||||
Thing.create_versioned_table
|
||||
end
|
||||
|
||||
def self.down
|
||||
Thing.drop_versioned_table
|
||||
drop_table "things" rescue nil
|
||||
end
|
||||
end
|
||||
43
vendor/plugins/acts_as_versioned/test/fixtures/page.rb
vendored
Normal file
43
vendor/plugins/acts_as_versioned/test/fixtures/page.rb
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
class Page < ActiveRecord::Base
|
||||
belongs_to :author
|
||||
has_many :authors, :through => :versions, :order => 'name'
|
||||
belongs_to :revisor, :class_name => 'Author'
|
||||
has_many :revisors, :class_name => 'Author', :through => :versions, :order => 'name'
|
||||
acts_as_versioned :if => :feeling_good? do
|
||||
def self.included(base)
|
||||
base.cattr_accessor :feeling_good
|
||||
base.feeling_good = true
|
||||
base.belongs_to :author
|
||||
base.belongs_to :revisor, :class_name => 'Author'
|
||||
end
|
||||
|
||||
def feeling_good?
|
||||
@@feeling_good == true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module LockedPageExtension
|
||||
def hello_world
|
||||
'hello_world'
|
||||
end
|
||||
end
|
||||
|
||||
class LockedPage < ActiveRecord::Base
|
||||
acts_as_versioned \
|
||||
:inheritance_column => :version_type,
|
||||
:foreign_key => :page_id,
|
||||
:table_name => :locked_pages_revisions,
|
||||
:class_name => 'LockedPageRevision',
|
||||
:version_column => :lock_version,
|
||||
:limit => 2,
|
||||
:if_changed => :title,
|
||||
:extend => LockedPageExtension
|
||||
end
|
||||
|
||||
class SpecialLockedPage < LockedPage
|
||||
end
|
||||
|
||||
class Author < ActiveRecord::Base
|
||||
has_many :pages
|
||||
end
|
||||
16
vendor/plugins/acts_as_versioned/test/fixtures/page_versions.yml
vendored
Normal file
16
vendor/plugins/acts_as_versioned/test/fixtures/page_versions.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
welcome_2:
|
||||
id: 1
|
||||
page_id: 1
|
||||
title: Welcome to the weblog
|
||||
body: Such a lovely day
|
||||
version: 24
|
||||
author_id: 1
|
||||
revisor_id: 1
|
||||
welcome_1:
|
||||
id: 2
|
||||
page_id: 1
|
||||
title: Welcome to the weblg
|
||||
body: Such a lovely day
|
||||
version: 23
|
||||
author_id: 2
|
||||
revisor_id: 2
|
||||
8
vendor/plugins/acts_as_versioned/test/fixtures/pages.yml
vendored
Normal file
8
vendor/plugins/acts_as_versioned/test/fixtures/pages.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
welcome:
|
||||
id: 1
|
||||
title: Welcome to the weblog
|
||||
body: Such a lovely day
|
||||
version: 24
|
||||
author_id: 1
|
||||
revisor_id: 1
|
||||
created_on: "2008-01-01 00:00:00"
|
||||
6
vendor/plugins/acts_as_versioned/test/fixtures/widget.rb
vendored
Normal file
6
vendor/plugins/acts_as_versioned/test/fixtures/widget.rb
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
class Widget < ActiveRecord::Base
|
||||
acts_as_versioned :sequence_name => 'widgets_seq', :association_options => {
|
||||
:dependent => :nullify, :order => 'version desc'
|
||||
}
|
||||
non_versioned_columns << 'foo'
|
||||
end
|
||||
46
vendor/plugins/acts_as_versioned/test/migration_test.rb
vendored
Normal file
46
vendor/plugins/acts_as_versioned/test/migration_test.rb
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
require File.join(File.dirname(__FILE__), 'abstract_unit')
|
||||
|
||||
if ActiveRecord::Base.connection.supports_migrations?
|
||||
class Thing < ActiveRecord::Base
|
||||
attr_accessor :version
|
||||
acts_as_versioned
|
||||
end
|
||||
|
||||
class MigrationTest < Test::Unit::TestCase
|
||||
self.use_transactional_fixtures = false
|
||||
def teardown
|
||||
if ActiveRecord::Base.connection.respond_to?(:initialize_schema_information)
|
||||
ActiveRecord::Base.connection.initialize_schema_information
|
||||
ActiveRecord::Base.connection.update "UPDATE schema_info SET version = 0"
|
||||
else
|
||||
ActiveRecord::Base.connection.initialize_schema_migrations_table
|
||||
ActiveRecord::Base.connection.assume_migrated_upto_version(0)
|
||||
end
|
||||
|
||||
Thing.connection.drop_table "things" rescue nil
|
||||
Thing.connection.drop_table "thing_versions" rescue nil
|
||||
Thing.reset_column_information
|
||||
end
|
||||
|
||||
def test_versioned_migration
|
||||
assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' }
|
||||
# take 'er up
|
||||
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
|
||||
t = Thing.create :title => 'blah blah', :price => 123.45, :type => 'Thing'
|
||||
assert_equal 1, t.versions.size
|
||||
|
||||
# check that the price column has remembered its value correctly
|
||||
assert_equal t.price, t.versions.first.price
|
||||
assert_equal t.title, t.versions.first.title
|
||||
assert_equal t[:type], t.versions.first[:type]
|
||||
|
||||
# make sure that the precision of the price column has been preserved
|
||||
assert_equal 7, Thing::Version.columns.find{|c| c.name == "price"}.precision
|
||||
assert_equal 2, Thing::Version.columns.find{|c| c.name == "price"}.scale
|
||||
|
||||
# now lets take 'er back down
|
||||
ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/')
|
||||
assert_raises(ActiveRecord::StatementInvalid) { Thing.create :title => 'blah blah' }
|
||||
end
|
||||
end
|
||||
end
|
||||
82
vendor/plugins/acts_as_versioned/test/schema.rb
vendored
Normal file
82
vendor/plugins/acts_as_versioned/test/schema.rb
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
ActiveRecord::Schema.define(:version => 0) do
|
||||
create_table :pages, :force => true do |t|
|
||||
t.column :version, :integer
|
||||
t.column :title, :string, :limit => 255
|
||||
t.column :body, :text
|
||||
t.column :created_on, :datetime
|
||||
t.column :updated_on, :datetime
|
||||
t.column :author_id, :integer
|
||||
t.column :revisor_id, :integer
|
||||
end
|
||||
|
||||
create_table :page_versions, :force => true do |t|
|
||||
t.column :page_id, :integer
|
||||
t.column :version, :integer
|
||||
t.column :title, :string, :limit => 255
|
||||
t.column :body, :text
|
||||
t.column :created_on, :datetime
|
||||
t.column :updated_on, :datetime
|
||||
t.column :author_id, :integer
|
||||
t.column :revisor_id, :integer
|
||||
end
|
||||
|
||||
add_index :page_versions, [:page_id, :version], :unique => true
|
||||
|
||||
create_table :authors, :force => true do |t|
|
||||
t.column :page_id, :integer
|
||||
t.column :name, :string
|
||||
end
|
||||
|
||||
create_table :locked_pages, :force => true do |t|
|
||||
t.column :lock_version, :integer
|
||||
t.column :title, :string, :limit => 255
|
||||
t.column :body, :text
|
||||
t.column :type, :string, :limit => 255
|
||||
end
|
||||
|
||||
create_table :locked_pages_revisions, :force => true do |t|
|
||||
t.column :page_id, :integer
|
||||
t.column :lock_version, :integer
|
||||
t.column :title, :string, :limit => 255
|
||||
t.column :body, :text
|
||||
t.column :version_type, :string, :limit => 255
|
||||
t.column :updated_at, :datetime
|
||||
end
|
||||
|
||||
add_index :locked_pages_revisions, [:page_id, :lock_version], :unique => true
|
||||
|
||||
create_table :widgets, :force => true do |t|
|
||||
t.column :name, :string, :limit => 50
|
||||
t.column :foo, :string
|
||||
t.column :version, :integer
|
||||
t.column :updated_at, :datetime
|
||||
end
|
||||
|
||||
create_table :widget_versions, :force => true do |t|
|
||||
t.column :widget_id, :integer
|
||||
t.column :name, :string, :limit => 50
|
||||
t.column :version, :integer
|
||||
t.column :updated_at, :datetime
|
||||
end
|
||||
|
||||
add_index :widget_versions, [:widget_id, :version], :unique => true
|
||||
|
||||
create_table :landmarks, :force => true do |t|
|
||||
t.column :name, :string
|
||||
t.column :latitude, :float
|
||||
t.column :longitude, :float
|
||||
t.column :doesnt_trigger_version,:string
|
||||
t.column :version, :integer
|
||||
end
|
||||
|
||||
create_table :landmark_versions, :force => true do |t|
|
||||
t.column :landmark_id, :integer
|
||||
t.column :name, :string
|
||||
t.column :latitude, :float
|
||||
t.column :longitude, :float
|
||||
t.column :doesnt_trigger_version,:string
|
||||
t.column :version, :integer
|
||||
end
|
||||
|
||||
add_index :landmark_versions, [:landmark_id, :version], :unique => true
|
||||
end
|
||||
370
vendor/plugins/acts_as_versioned/test/versioned_test.rb
vendored
Normal file
370
vendor/plugins/acts_as_versioned/test/versioned_test.rb
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
require File.join(File.dirname(__FILE__), 'abstract_unit')
|
||||
require File.join(File.dirname(__FILE__), 'fixtures/page')
|
||||
require File.join(File.dirname(__FILE__), 'fixtures/widget')
|
||||
|
||||
class VersionedTest < Test::Unit::TestCase
|
||||
fixtures :pages, :page_versions, :locked_pages, :locked_pages_revisions, :authors, :landmarks, :landmark_versions
|
||||
set_fixture_class :page_versions => Page::Version
|
||||
|
||||
def test_saves_versioned_copy
|
||||
p = Page.create! :title => 'first title', :body => 'first body'
|
||||
assert !p.new_record?
|
||||
assert_equal 1, p.versions.size
|
||||
assert_equal 1, p.version
|
||||
assert_instance_of Page.versioned_class, p.versions.first
|
||||
end
|
||||
|
||||
def test_saves_without_revision
|
||||
p = pages(:welcome)
|
||||
old_versions = p.versions.count
|
||||
|
||||
p.save_without_revision
|
||||
|
||||
p.without_revision do
|
||||
p.update_attributes :title => 'changed'
|
||||
end
|
||||
|
||||
assert_equal old_versions, p.versions.count
|
||||
end
|
||||
|
||||
def test_rollback_with_version_number
|
||||
p = pages(:welcome)
|
||||
assert_equal 24, p.version
|
||||
assert_equal 'Welcome to the weblog', p.title
|
||||
|
||||
assert p.revert_to!(23), "Couldn't revert to 23"
|
||||
assert_equal 23, p.version
|
||||
assert_equal 'Welcome to the weblg', p.title
|
||||
end
|
||||
|
||||
def test_versioned_class_name
|
||||
assert_equal 'Version', Page.versioned_class_name
|
||||
assert_equal 'LockedPageRevision', LockedPage.versioned_class_name
|
||||
end
|
||||
|
||||
def test_versioned_class
|
||||
assert_equal Page::Version, Page.versioned_class
|
||||
assert_equal LockedPage::LockedPageRevision, LockedPage.versioned_class
|
||||
end
|
||||
|
||||
def test_special_methods
|
||||
assert_nothing_raised { pages(:welcome).feeling_good? }
|
||||
assert_nothing_raised { pages(:welcome).versions.first.feeling_good? }
|
||||
assert_nothing_raised { locked_pages(:welcome).hello_world }
|
||||
assert_nothing_raised { locked_pages(:welcome).versions.first.hello_world }
|
||||
end
|
||||
|
||||
def test_rollback_with_version_class
|
||||
p = pages(:welcome)
|
||||
assert_equal 24, p.version
|
||||
assert_equal 'Welcome to the weblog', p.title
|
||||
|
||||
assert p.revert_to!(p.versions.find_by_version(23)), "Couldn't revert to 23"
|
||||
assert_equal 23, p.version
|
||||
assert_equal 'Welcome to the weblg', p.title
|
||||
end
|
||||
|
||||
def test_rollback_fails_with_invalid_revision
|
||||
p = locked_pages(:welcome)
|
||||
assert !p.revert_to!(locked_pages(:thinking))
|
||||
end
|
||||
|
||||
def test_saves_versioned_copy_with_options
|
||||
p = LockedPage.create! :title => 'first title'
|
||||
assert !p.new_record?
|
||||
assert_equal 1, p.versions.size
|
||||
assert_instance_of LockedPage.versioned_class, p.versions.first
|
||||
end
|
||||
|
||||
def test_rollback_with_version_number_with_options
|
||||
p = locked_pages(:welcome)
|
||||
assert_equal 'Welcome to the weblog', p.title
|
||||
assert_equal 'LockedPage', p.versions.first.version_type
|
||||
|
||||
assert p.revert_to!(p.versions.first.lock_version), "Couldn't revert to 23"
|
||||
assert_equal 'Welcome to the weblg', p.title
|
||||
assert_equal 'LockedPage', p.versions.first.version_type
|
||||
end
|
||||
|
||||
def test_rollback_with_version_class_with_options
|
||||
p = locked_pages(:welcome)
|
||||
assert_equal 'Welcome to the weblog', p.title
|
||||
assert_equal 'LockedPage', p.versions.first.version_type
|
||||
|
||||
assert p.revert_to!(p.versions.first), "Couldn't revert to 1"
|
||||
assert_equal 'Welcome to the weblg', p.title
|
||||
assert_equal 'LockedPage', p.versions.first.version_type
|
||||
end
|
||||
|
||||
def test_saves_versioned_copy_with_sti
|
||||
p = SpecialLockedPage.create! :title => 'first title'
|
||||
assert !p.new_record?
|
||||
assert_equal 1, p.versions.size
|
||||
assert_instance_of LockedPage.versioned_class, p.versions.first
|
||||
assert_equal 'SpecialLockedPage', p.versions.first.version_type
|
||||
end
|
||||
|
||||
def test_rollback_with_version_number_with_sti
|
||||
p = locked_pages(:thinking)
|
||||
assert_equal 'So I was thinking', p.title
|
||||
|
||||
assert p.revert_to!(p.versions.first.lock_version), "Couldn't revert to 1"
|
||||
assert_equal 'So I was thinking!!!', p.title
|
||||
assert_equal 'SpecialLockedPage', p.versions.first.version_type
|
||||
end
|
||||
|
||||
def test_lock_version_works_with_versioning
|
||||
p = locked_pages(:thinking)
|
||||
p2 = LockedPage.find(p.id)
|
||||
|
||||
p.title = 'fresh title'
|
||||
p.save
|
||||
assert_equal 2, p.versions.size # limit!
|
||||
|
||||
assert_raises(ActiveRecord::StaleObjectError) do
|
||||
p2.title = 'stale title'
|
||||
p2.save
|
||||
end
|
||||
end
|
||||
|
||||
def test_version_if_condition
|
||||
p = Page.create! :title => "title"
|
||||
assert_equal 1, p.version
|
||||
|
||||
Page.feeling_good = false
|
||||
p.save
|
||||
assert_equal 1, p.version
|
||||
Page.feeling_good = true
|
||||
end
|
||||
|
||||
def test_version_if_condition2
|
||||
# set new if condition
|
||||
Page.class_eval do
|
||||
def new_feeling_good() title[0..0] == 'a'; end
|
||||
alias_method :old_feeling_good, :feeling_good?
|
||||
alias_method :feeling_good?, :new_feeling_good
|
||||
end
|
||||
|
||||
p = Page.create! :title => "title"
|
||||
assert_equal 1, p.version # version does not increment
|
||||
assert_equal 1, p.versions.count
|
||||
|
||||
p.update_attributes(:title => 'new title')
|
||||
assert_equal 1, p.version # version does not increment
|
||||
assert_equal 1, p.versions.count
|
||||
|
||||
p.update_attributes(:title => 'a title')
|
||||
assert_equal 2, p.version
|
||||
assert_equal 2, p.versions.count
|
||||
|
||||
# reset original if condition
|
||||
Page.class_eval { alias_method :feeling_good?, :old_feeling_good }
|
||||
end
|
||||
|
||||
def test_version_if_condition_with_block
|
||||
# set new if condition
|
||||
old_condition = Page.version_condition
|
||||
Page.version_condition = Proc.new { |page| page.title[0..0] == 'b' }
|
||||
|
||||
p = Page.create! :title => "title"
|
||||
assert_equal 1, p.version # version does not increment
|
||||
assert_equal 1, p.versions.count
|
||||
|
||||
p.update_attributes(:title => 'a title')
|
||||
assert_equal 1, p.version # version does not increment
|
||||
assert_equal 1, p.versions.count
|
||||
|
||||
p.update_attributes(:title => 'b title')
|
||||
assert_equal 2, p.version
|
||||
assert_equal 2, p.versions.count
|
||||
|
||||
# reset original if condition
|
||||
Page.version_condition = old_condition
|
||||
end
|
||||
|
||||
def test_version_no_limit
|
||||
p = Page.create! :title => "title", :body => 'first body'
|
||||
p.save
|
||||
p.save
|
||||
5.times do |i|
|
||||
p.title = "title#{i}"
|
||||
p.save
|
||||
assert_equal "title#{i}", p.title
|
||||
assert_equal (i+2), p.version
|
||||
end
|
||||
end
|
||||
|
||||
def test_version_max_limit
|
||||
p = LockedPage.create! :title => "title"
|
||||
p.update_attributes(:title => "title1")
|
||||
p.update_attributes(:title => "title2")
|
||||
5.times do |i|
|
||||
p.title = "title#{i}"
|
||||
p.save
|
||||
assert_equal "title#{i}", p.title
|
||||
assert_equal (i+4), p.lock_version
|
||||
assert p.versions(true).size <= 2, "locked version can only store 2 versions"
|
||||
end
|
||||
end
|
||||
|
||||
def test_track_altered_attributes_default_value
|
||||
assert !Page.track_altered_attributes
|
||||
assert LockedPage.track_altered_attributes
|
||||
assert SpecialLockedPage.track_altered_attributes
|
||||
end
|
||||
|
||||
def test_track_altered_attributes
|
||||
p = LockedPage.create! :title => "title"
|
||||
assert_equal 1, p.lock_version
|
||||
assert_equal 1, p.versions(true).size
|
||||
|
||||
p.body = 'whoa'
|
||||
assert !p.save_version?
|
||||
p.save
|
||||
assert_equal 2, p.lock_version # still increments version because of optimistic locking
|
||||
assert_equal 1, p.versions(true).size
|
||||
|
||||
p.title = 'updated title'
|
||||
assert p.save_version?
|
||||
p.save
|
||||
assert_equal 3, p.lock_version
|
||||
assert_equal 1, p.versions(true).size # version 1 deleted
|
||||
|
||||
p.title = 'updated title!'
|
||||
assert p.save_version?
|
||||
p.save
|
||||
assert_equal 4, p.lock_version
|
||||
assert_equal 2, p.versions(true).size # version 1 deleted
|
||||
end
|
||||
|
||||
def test_find_versions
|
||||
assert_equal 1, locked_pages(:welcome).versions.find(:all, :conditions => ['title LIKE ?', '%weblog%']).size
|
||||
end
|
||||
|
||||
def test_find_version
|
||||
assert_equal page_versions(:welcome_1), pages(:welcome).versions.find_by_version(23)
|
||||
end
|
||||
|
||||
def test_with_sequence
|
||||
assert_equal 'widgets_seq', Widget.versioned_class.sequence_name
|
||||
3.times { Widget.create! :name => 'new widget' }
|
||||
assert_equal 3, Widget.count
|
||||
assert_equal 3, Widget.versioned_class.count
|
||||
end
|
||||
|
||||
def test_has_many_through
|
||||
assert_equal [authors(:caged), authors(:mly)], pages(:welcome).authors
|
||||
end
|
||||
|
||||
def test_has_many_through_with_custom_association
|
||||
assert_equal [authors(:caged), authors(:mly)], pages(:welcome).revisors
|
||||
end
|
||||
|
||||
def test_referential_integrity
|
||||
pages(:welcome).destroy
|
||||
assert_equal 0, Page.count
|
||||
assert_equal 0, Page::Version.count
|
||||
end
|
||||
|
||||
def test_association_options
|
||||
association = Page.reflect_on_association(:versions)
|
||||
options = association.options
|
||||
assert_equal :delete_all, options[:dependent]
|
||||
|
||||
association = Widget.reflect_on_association(:versions)
|
||||
options = association.options
|
||||
assert_equal :nullify, options[:dependent]
|
||||
assert_equal 'version desc', options[:order]
|
||||
assert_equal 'widget_id', options[:foreign_key]
|
||||
|
||||
widget = Widget.create! :name => 'new widget'
|
||||
assert_equal 1, Widget.count
|
||||
assert_equal 1, Widget.versioned_class.count
|
||||
widget.destroy
|
||||
assert_equal 0, Widget.count
|
||||
assert_equal 1, Widget.versioned_class.count
|
||||
end
|
||||
|
||||
def test_versioned_records_should_belong_to_parent
|
||||
page = pages(:welcome)
|
||||
page_version = page.versions.last
|
||||
assert_equal page, page_version.page
|
||||
end
|
||||
|
||||
def test_unaltered_attributes
|
||||
landmarks(:washington).attributes = landmarks(:washington).attributes.except("id")
|
||||
assert !landmarks(:washington).changed?
|
||||
end
|
||||
|
||||
def test_unchanged_string_attributes
|
||||
landmarks(:washington).attributes = landmarks(:washington).attributes.except("id").inject({}) { |params, (key, value)| params.update(key => value.to_s) }
|
||||
assert !landmarks(:washington).changed?
|
||||
end
|
||||
|
||||
def test_should_find_earliest_version
|
||||
assert_equal page_versions(:welcome_1), pages(:welcome).versions.earliest
|
||||
end
|
||||
|
||||
def test_should_find_latest_version
|
||||
assert_equal page_versions(:welcome_2), pages(:welcome).versions.latest
|
||||
end
|
||||
|
||||
def test_should_find_previous_version
|
||||
assert_equal page_versions(:welcome_1), page_versions(:welcome_2).previous
|
||||
assert_equal page_versions(:welcome_1), pages(:welcome).versions.before(page_versions(:welcome_2))
|
||||
end
|
||||
|
||||
def test_should_find_next_version
|
||||
assert_equal page_versions(:welcome_2), page_versions(:welcome_1).next
|
||||
assert_equal page_versions(:welcome_2), pages(:welcome).versions.after(page_versions(:welcome_1))
|
||||
end
|
||||
|
||||
def test_should_find_version_count
|
||||
assert_equal 2, pages(:welcome).versions.size
|
||||
end
|
||||
|
||||
def test_if_changed_creates_version_if_a_listed_column_is_changed
|
||||
landmarks(:washington).name = "Washington"
|
||||
assert landmarks(:washington).changed?
|
||||
assert landmarks(:washington).altered?
|
||||
end
|
||||
|
||||
def test_if_changed_creates_version_if_all_listed_columns_are_changed
|
||||
landmarks(:washington).name = "Washington"
|
||||
landmarks(:washington).latitude = 1.0
|
||||
landmarks(:washington).longitude = 1.0
|
||||
assert landmarks(:washington).changed?
|
||||
assert landmarks(:washington).altered?
|
||||
end
|
||||
|
||||
def test_if_changed_does_not_create_new_version_if_unlisted_column_is_changed
|
||||
landmarks(:washington).doesnt_trigger_version = "This should not trigger version"
|
||||
assert landmarks(:washington).changed?
|
||||
assert !landmarks(:washington).altered?
|
||||
end
|
||||
|
||||
def test_without_locking_temporarily_disables_optimistic_locking
|
||||
enabled1 = false
|
||||
block_called = false
|
||||
|
||||
ActiveRecord::Base.lock_optimistically = true
|
||||
LockedPage.without_locking do
|
||||
enabled1 = ActiveRecord::Base.lock_optimistically
|
||||
block_called = true
|
||||
end
|
||||
enabled2 = ActiveRecord::Base.lock_optimistically
|
||||
|
||||
assert block_called
|
||||
assert !enabled1
|
||||
assert enabled2
|
||||
end
|
||||
|
||||
def test_without_locking_reverts_optimistic_locking_settings_if_block_raises_exception
|
||||
assert_raises(RuntimeError) do
|
||||
LockedPage.without_locking do
|
||||
raise RuntimeError, "oh noes"
|
||||
end
|
||||
end
|
||||
assert ActiveRecord::Base.lock_optimistically
|
||||
end
|
||||
end
|
||||
91
vendor/plugins/asset_packager/README
vendored
91
vendor/plugins/asset_packager/README
vendored
@@ -13,15 +13,9 @@ speed and saving bandwidth.
|
||||
When in development, it allows you to use your original versions
|
||||
and retain formatting and comments for readability and debugging.
|
||||
|
||||
Because not all browsers will dependably cache JavaScript and CSS
|
||||
files with query string parameters, AssetPackager writes a timestamp
|
||||
or subversion revision stamp (if available) into the merged file names.
|
||||
Therefore files are correctly cached by the browser AND your users
|
||||
always get the latest version when you re-deploy.
|
||||
|
||||
This code is released under the MIT license (like Ruby). You<6F>re free
|
||||
This code is released under the MIT license (like Ruby). You're free
|
||||
to rip it up, enhance it, etc. And if you make any enhancements,
|
||||
I<EFBFBD>d like to know so I can add them back in. Thanks!
|
||||
I'd like to know so I can add them back in. Thanks!
|
||||
|
||||
* Formerly known as MergeJS.
|
||||
|
||||
@@ -39,22 +33,30 @@ http://www.crockford.com/javascript/jsmin.html
|
||||
|
||||
* Merges and compresses JavaScript and CSS when running in production.
|
||||
* Uses uncompressed originals when running in development.
|
||||
* Handles caching correctly. (No querystring parameters - filename timestamps)
|
||||
* Versions each package individually. Updates to files in one won't re-trigger downloading the others.
|
||||
* Uses subversion revision numbers instead of timestamps if within a subversion controlled directory.
|
||||
* Guarantees new version will get downloaded the next time you deploy.
|
||||
* Generates packages on demand in production
|
||||
|
||||
== Components
|
||||
|
||||
* Rake Task for merging and compressing JavaScript and CSS files.
|
||||
* Rake tasks for managing packages
|
||||
* Helper functions for including these JavaScript and CSS files in your views.
|
||||
* YAML configuration file for mapping JavaScript and CSS files to merged versions.
|
||||
* YAML configuration file for mapping JavaScript and CSS files to packages.
|
||||
* Rake Task for auto-generating the YAML file from your existing JavaScript files.
|
||||
|
||||
== Updates
|
||||
|
||||
November '08:
|
||||
* Rails 2.2 compatibility fixes
|
||||
* No more mucking with internal Rails functions, which means:
|
||||
* Return to use of query-string timestamps. Greatly simplifies things.
|
||||
* Multiple asset-hosts supported
|
||||
* Filenames with "."'s in them, such as "jquery-x.x.x" are supported.
|
||||
* Now compatible with any revision control system since it no longer uses revision numbers.
|
||||
* Packages generated on demand in production mode. Running create_all rake task no longer necessary.
|
||||
|
||||
== How to Use:
|
||||
|
||||
1. Download and install the plugin:
|
||||
./script/plugin install http://sbecker.net/shared/plugins/asset_packager
|
||||
./script/plugin install git://github.com/sbecker/asset_packager.git
|
||||
|
||||
2. Run the rake task "asset:packager:create_yml" to generate the /config/asset_packages.yml
|
||||
file the first time. You will need to reorder files under 'base' so dependencies are loaded
|
||||
@@ -64,6 +66,8 @@ IMPORTANT: JavaScript files can break once compressed if each statement doesn't
|
||||
The minifier puts multiple statements on one line, so if the semi-colon is missing, the statement may no
|
||||
longer makes sense and cause a syntax error.
|
||||
|
||||
== Examples of config/asset_packages.yml
|
||||
|
||||
Example from a fresh rails app after running the rake task. (Stylesheets is blank because a
|
||||
default rails app has no stylesheets yet.):
|
||||
|
||||
@@ -78,7 +82,7 @@ javascripts:
|
||||
stylesheets:
|
||||
- base: []
|
||||
|
||||
Example with multiple merged files:
|
||||
Multiple packages:
|
||||
|
||||
---
|
||||
javascripts:
|
||||
@@ -101,8 +105,13 @@ stylesheets:
|
||||
|
||||
3. Run the rake task "asset:packager:build_all" to generate the compressed, merged versions
|
||||
for each package. Whenever you rearrange the yaml file, you'll need to run this task again.
|
||||
|
||||
Merging and compressing is expensive, so this is something we want to do once, not every time
|
||||
your app starts. Thats why its a rake task.
|
||||
your app starts. Thats why its a rake task. You can run this task via Capistrano when deploying
|
||||
to avoid an initially slow request the first time a page is generated.
|
||||
|
||||
Note: The package will be generated on the fly if it doesn't yet exist, so you don't *need*
|
||||
to run the rake task when deploying, its just recommended for speeding up initial requests.
|
||||
|
||||
4. Use the helper functions whenever including these files in your application. See below for examples.
|
||||
|
||||
@@ -111,46 +120,43 @@ away some CSS hackery. To disable comment removal, comment out /lib/synthesis/as
|
||||
|
||||
== JavaScript Examples
|
||||
|
||||
Example call:
|
||||
<%= javascript_include_merged 'prototype', 'effects', 'controls', 'dragdrop', 'application', 'foo', 'bar' %>
|
||||
Example call (based on above /config/asset_packages.yml):
|
||||
<%= javascript_include_merged :base %>
|
||||
|
||||
In development, this generates:
|
||||
<script type="text/javascript" src="/javascripts/prototype.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/effects.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/controls.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/dragdrop.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/application.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/foo.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/bar.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/prototype.js?1228027240"></script>
|
||||
<script type="text/javascript" src="/javascripts/effects.js?1228027240"></script>
|
||||
<script type="text/javascript" src="/javascripts/controls.js?1228027240"></script>
|
||||
<script type="text/javascript" src="/javascripts/dragdrop.js?1228027240"></script>
|
||||
<script type="text/javascript" src="/javascripts/application.js?1228027240"></script>
|
||||
|
||||
In production, this generates:
|
||||
<script type="text/javascript" src="/javascripts/base_1150571523.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/secondary_1150729166.js"></script>
|
||||
|
||||
Now supports symbols and :defaults as well:
|
||||
<%= javascript_include_merged :defaults %>
|
||||
<%= javascript_include_merged :foo, :bar %>
|
||||
<script type="text/javascript" src="/javascripts/base_packaged.js?123456789"></script>
|
||||
|
||||
== Stylesheet Examples
|
||||
|
||||
Example call:
|
||||
<%= stylesheet_link_merged 'screen', 'header' %>
|
||||
<%= stylesheet_link_merged :base %>
|
||||
|
||||
In development, this generates:
|
||||
<link href="/stylesheets/screen.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
<link href="/stylesheets/header.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
<link href="/stylesheets/screen.css?1228027240" media="screen" rel="Stylesheet" type="text/css" />
|
||||
<link href="/stylesheets/header.css?1228027240" media="screen" rel="Stylesheet" type="text/css" />
|
||||
|
||||
In production this generates:
|
||||
<link href="/stylesheets/base_1150729166.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
<link href="/stylesheets/base_packaged.css?1228027240" media="screen" rel="Stylesheet" type="text/css" />
|
||||
|
||||
== Different CSS Media
|
||||
|
||||
All options for stylesheet_link_tag still work, so if you want to specify a different media type:
|
||||
<%= stylesheet_link_merged :secondary, 'media' => 'print' %>
|
||||
|
||||
== Running the tests
|
||||
== Rake tasks
|
||||
|
||||
So you want to run the tests eh? Ok, then listen:
|
||||
rake asset:packager:build_all # Merge and compress assets
|
||||
rake asset:packager:create_yml # Generate asset_packages.yml from existing assets
|
||||
rake asset:packager:delete_all # Delete all asset builds
|
||||
|
||||
== Running the tests
|
||||
|
||||
This plugin has a full suite of tests. But since they
|
||||
depend on rails, it has to be run in the context of a
|
||||
@@ -158,13 +164,12 @@ rails app, in the vendor/plugins directory. Observe:
|
||||
|
||||
> rails newtestapp
|
||||
> cd newtestapp
|
||||
> ./script/plugin install http://sbecker.net/shared/plugins/asset_packager
|
||||
> cd vendor/plugins/asset_packager/
|
||||
> rake # all tests pass
|
||||
> ./script/plugin install ./script/plugin install git://github.com/sbecker/asset_packager.git
|
||||
> rake test:plugins PLUGIN=asset_packager # all tests pass
|
||||
|
||||
== License
|
||||
Copyright (c) 2006 Scott Becker - http://synthesis.sbecker.net
|
||||
Contact Email: becker.scott@gmail.com
|
||||
Copyright (c) 2006-2008 Scott Becker - http://synthesis.sbecker.net
|
||||
Contact via Github for change requests, etc.
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ module Synthesis
|
||||
|
||||
def delete_all
|
||||
@@asset_packages_yml.keys.each do |asset_type|
|
||||
@@asset_packages_yml[asset_type].each { |p| self.new(asset_type, p).delete_all_builds }
|
||||
@@asset_packages_yml[asset_type].each { |p| self.new(asset_type, p).delete_previous_build }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -102,61 +102,38 @@ module Synthesis
|
||||
@asset_path = ($asset_base_path ? "#{$asset_base_path}/" : "#{RAILS_ROOT}/public/") +
|
||||
"#{@asset_type}#{@target_dir.gsub(/^(.+)$/, '/\1')}"
|
||||
@extension = get_extension
|
||||
@match_regex = Regexp.new("\\A#{@target}_\\d+.#{@extension}\\z")
|
||||
@file_name = "#{@target}_packaged.#{@extension}"
|
||||
@full_path = File.join(@asset_path, @file_name)
|
||||
end
|
||||
|
||||
def package_exists?
|
||||
File.exists?(@full_path)
|
||||
end
|
||||
|
||||
def current_file
|
||||
@target_dir.gsub(/^(.+)$/, '\1/') +
|
||||
Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.sort.reverse[0].chomp(".#{@extension}")
|
||||
build unless package_exists?
|
||||
|
||||
path = @target_dir.gsub(/^(.+)$/, '\1/')
|
||||
"#{path}#{@target}_packaged"
|
||||
end
|
||||
|
||||
def build
|
||||
delete_old_builds
|
||||
delete_previous_build
|
||||
create_new_build
|
||||
end
|
||||
|
||||
def delete_old_builds
|
||||
Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.each do |x|
|
||||
File.delete("#{@asset_path}/#{x}") unless x.index(revision.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_all_builds
|
||||
Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.each do |x|
|
||||
File.delete("#{@asset_path}/#{x}")
|
||||
end
|
||||
def delete_previous_build
|
||||
File.delete(@full_path) if File.exists?(@full_path)
|
||||
end
|
||||
|
||||
private
|
||||
def revision
|
||||
unless @revision
|
||||
revisions = [1]
|
||||
@sources.each do |source|
|
||||
revisions << get_file_revision("#{@asset_path}/#{source}.#{@extension}")
|
||||
end
|
||||
@revision = revisions.max
|
||||
end
|
||||
@revision
|
||||
end
|
||||
|
||||
def get_file_revision(path)
|
||||
if File.exists?(path)
|
||||
begin
|
||||
`svn info #{path}`[/Last Changed Rev: (.*?)\n/][/(\d+)/].to_i
|
||||
rescue # use filename timestamp if not in subversion
|
||||
File.mtime(path).to_i
|
||||
end
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def create_new_build
|
||||
if File.exists?("#{@asset_path}/#{@target}_#{revision}.#{@extension}")
|
||||
log "Latest version already exists: #{@asset_path}/#{@target}_#{revision}.#{@extension}"
|
||||
new_build_path = "#{@asset_path}/#{@target}_packaged.#{@extension}"
|
||||
if File.exists?(new_build_path)
|
||||
log "Latest version already exists: #{new_build_path}"
|
||||
else
|
||||
File.open("#{@asset_path}/#{@target}_#{revision}.#{@extension}", "w") {|f| f.write(compressed_file) }
|
||||
log "Created #{@asset_path}/#{@target}_#{revision}.#{@extension}"
|
||||
File.open(new_build_path, "w") {|f| f.write(compressed_file) }
|
||||
log "Created #{new_build_path}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -179,7 +156,7 @@ module Synthesis
|
||||
|
||||
def compress_js(source)
|
||||
jsmin_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/lib"
|
||||
tmp_path = "#{RAILS_ROOT}/tmp/#{@target}_#{revision}"
|
||||
tmp_path = "#{RAILS_ROOT}/tmp/#{@target}_packaged"
|
||||
|
||||
# write out to a temp file
|
||||
File.open("#{tmp_path}_uncompressed.js", "w") {|f| f.write(source) }
|
||||
@@ -200,7 +177,7 @@ module Synthesis
|
||||
|
||||
def compress_css(source)
|
||||
source.gsub!(/\s+/, " ") # collapse space
|
||||
source.gsub!(/\/\*(.*?)\*\/ /, "") # remove comments - caution, might want to remove this if using css hacks
|
||||
source.gsub!(/\/\*(.*?)\*\//, "") # remove comments - caution, might want to remove this if using css hacks
|
||||
source.gsub!(/\} /, "}\n") # add line breaks
|
||||
source.gsub!(/\n$/, "") # remove last break
|
||||
source.gsub!(/ \{ /, " {") # trim inside brackets
|
||||
|
||||
@@ -32,36 +32,8 @@ module Synthesis
|
||||
AssetPackage.targets_from_sources("stylesheets", sources) :
|
||||
AssetPackage.sources_from_targets("stylesheets", sources))
|
||||
|
||||
sources.collect { |source|
|
||||
source = stylesheet_path(source)
|
||||
tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options))
|
||||
}.join("\n")
|
||||
sources.collect { |source| stylesheet_link_tag(source, options) }.join("\n")
|
||||
end
|
||||
|
||||
private
|
||||
# rewrite compute_public_path to allow us to not include the query string timestamp
|
||||
# used by ActionView::Helpers::AssetTagHelper
|
||||
def compute_public_path(source, dir, ext=nil, add_asset_id=true)
|
||||
source = source.dup
|
||||
source << ".#{ext}" if File.extname(source).blank? && ext
|
||||
unless source =~ %r{^[-a-z]+://}
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
asset_id = rails_asset_id(source)
|
||||
source << '?' + asset_id if defined?(RAILS_ROOT) and add_asset_id and not asset_id.blank?
|
||||
source = "#{ActionController::Base.asset_host}#{@controller.request.relative_url_root}#{source}"
|
||||
end
|
||||
source
|
||||
end
|
||||
|
||||
# rewrite javascript path function to not include query string timestamp
|
||||
def javascript_path(source)
|
||||
compute_public_path(source, 'javascripts', 'js', false)
|
||||
end
|
||||
|
||||
# rewrite stylesheet path function to not include query string timestamp
|
||||
def stylesheet_path(source)
|
||||
compute_public_path(source, 'stylesheets', 'css', false)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -9,13 +9,12 @@ require 'mocha'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
ActionController::Base.logger = nil
|
||||
ActionController::Base.ignore_missing_templates = false
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
|
||||
$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml")
|
||||
$asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets"
|
||||
|
||||
class AssetPackageHelperProductionTest < Test::Unit::TestCase
|
||||
class AssetPackageHelperDevelopmentTest < Test::Unit::TestCase
|
||||
include ActionView::Helpers::TagHelper
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include Synthesis::AssetPackageHelper
|
||||
@@ -24,24 +23,18 @@ class AssetPackageHelperProductionTest < Test::Unit::TestCase
|
||||
Synthesis::AssetPackage.any_instance.stubs(:log)
|
||||
|
||||
@controller = Class.new do
|
||||
attr_reader :request
|
||||
def initialize
|
||||
@request = Class.new do
|
||||
def relative_url_root
|
||||
""
|
||||
end
|
||||
end.new
|
||||
def request
|
||||
@request ||= ActionController::TestRequest.new
|
||||
end
|
||||
|
||||
end.new
|
||||
end
|
||||
|
||||
def build_js_expected_string(*sources)
|
||||
sources.map {|s| %(<script src="/javascripts/#{s}.js" type="text/javascript"></script>) }.join("\n")
|
||||
sources.map {|s| javascript_include_tag(s) }.join("\n")
|
||||
end
|
||||
|
||||
def build_css_expected_string(*sources)
|
||||
sources.map {|s| %(<link href="/stylesheets/#{s}.css" rel="Stylesheet" type="text/css" media="screen" />) }.join("\n")
|
||||
sources.map {|s| stylesheet_link_tag(s) }.join("\n")
|
||||
end
|
||||
|
||||
def test_js_basic
|
||||
|
||||
@@ -8,7 +8,6 @@ require 'mocha'
|
||||
require 'action_controller/test_process'
|
||||
|
||||
ActionController::Base.logger = nil
|
||||
ActionController::Base.ignore_missing_templates = false
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
|
||||
$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml")
|
||||
@@ -26,16 +25,9 @@ class AssetPackageHelperProductionTest < Test::Unit::TestCase
|
||||
self.stubs(:should_merge?).returns(true)
|
||||
|
||||
@controller = Class.new do
|
||||
|
||||
attr_reader :request
|
||||
def initialize
|
||||
@request = Class.new do
|
||||
def relative_url_root
|
||||
""
|
||||
end
|
||||
end.new
|
||||
def request
|
||||
@request ||= ActionController::TestRequest.new
|
||||
end
|
||||
|
||||
end.new
|
||||
|
||||
build_packages_once
|
||||
@@ -49,11 +41,11 @@ class AssetPackageHelperProductionTest < Test::Unit::TestCase
|
||||
end
|
||||
|
||||
def build_js_expected_string(*sources)
|
||||
sources.map {|s| %(<script src="/javascripts/#{s}.js" type="text/javascript"></script>) }.join("\n")
|
||||
sources.map {|s| javascript_include_tag(s) }.join("\n")
|
||||
end
|
||||
|
||||
def build_css_expected_string(*sources)
|
||||
sources.map {|s| %(<link href="/stylesheets/#{s}.css" rel="Stylesheet" type="text/css" media="screen" />) }.join("\n")
|
||||
sources.map {|s| stylesheet_link_tag(s) }.join("\n")
|
||||
end
|
||||
|
||||
def test_js_basic
|
||||
@@ -145,9 +137,4 @@ class AssetPackageHelperProductionTest < Test::Unit::TestCase
|
||||
stylesheet_link_merged(:base, :secondary, "subdir/styles")
|
||||
end
|
||||
|
||||
def test_image_tag
|
||||
timestamp = rails_asset_id("images/rails.png")
|
||||
assert_dom_equal %(<img alt="Rails" src="/images/rails.png?#{timestamp}" />), image_tag("rails")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -38,44 +38,44 @@ class AssetPackagerTest < Test::Unit::TestCase
|
||||
|
||||
def test_delete_and_build
|
||||
Synthesis::AssetPackage.delete_all
|
||||
js_package_names = Dir.new("#{$asset_base_path}/javascripts").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.js/) }
|
||||
css_package_names = Dir.new("#{$asset_base_path}/stylesheets").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }
|
||||
css_subdir_package_names = Dir.new("#{$asset_base_path}/stylesheets/subdir").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }
|
||||
js_package_names = Dir.new("#{$asset_base_path}/javascripts").entries.delete_if { |x| ! (x =~ /\A\w+_packaged.js/) }
|
||||
css_package_names = Dir.new("#{$asset_base_path}/stylesheets").entries.delete_if { |x| ! (x =~ /\A\w+_packaged.css/) }
|
||||
css_subdir_package_names = Dir.new("#{$asset_base_path}/stylesheets/subdir").entries.delete_if { |x| ! (x =~ /\A\w+_packaged.css/) }
|
||||
|
||||
assert_equal 0, js_package_names.length
|
||||
assert_equal 0, css_package_names.length
|
||||
assert_equal 0, css_subdir_package_names.length
|
||||
|
||||
Synthesis::AssetPackage.build_all
|
||||
js_package_names = Dir.new("#{$asset_base_path}/javascripts").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.js/) }.sort
|
||||
css_package_names = Dir.new("#{$asset_base_path}/stylesheets").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }.sort
|
||||
css_subdir_package_names = Dir.new("#{$asset_base_path}/stylesheets/subdir").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }.sort
|
||||
js_package_names = Dir.new("#{$asset_base_path}/javascripts").entries.delete_if { |x| ! (x =~ /\A\w+_packaged.js/) }.sort
|
||||
css_package_names = Dir.new("#{$asset_base_path}/stylesheets").entries.delete_if { |x| ! (x =~ /\A\w+_packaged.css/) }.sort
|
||||
css_subdir_package_names = Dir.new("#{$asset_base_path}/stylesheets/subdir").entries.delete_if { |x| ! (x =~ /\A\w+_packaged.css/) }.sort
|
||||
|
||||
assert_equal 2, js_package_names.length
|
||||
assert_equal 2, css_package_names.length
|
||||
assert_equal 1, css_subdir_package_names.length
|
||||
assert js_package_names[0].match(/\Abase_\d+.js\z/)
|
||||
assert js_package_names[1].match(/\Asecondary_\d+.js\z/)
|
||||
assert css_package_names[0].match(/\Abase_\d+.css\z/)
|
||||
assert css_package_names[1].match(/\Asecondary_\d+.css\z/)
|
||||
assert css_subdir_package_names[0].match(/\Astyles_\d+.css\z/)
|
||||
assert js_package_names[0].match(/\Abase_packaged.js\z/)
|
||||
assert js_package_names[1].match(/\Asecondary_packaged.js\z/)
|
||||
assert css_package_names[0].match(/\Abase_packaged.css\z/)
|
||||
assert css_package_names[1].match(/\Asecondary_packaged.css\z/)
|
||||
assert css_subdir_package_names[0].match(/\Astyles_packaged.css\z/)
|
||||
end
|
||||
|
||||
def test_js_names_from_sources
|
||||
package_names = Synthesis::AssetPackage.targets_from_sources("javascripts", ["prototype", "effects", "noexist1", "controls", "foo", "noexist2"])
|
||||
assert_equal 4, package_names.length
|
||||
assert package_names[0].match(/\Abase_\d+\z/)
|
||||
assert package_names[0].match(/\Abase_packaged\z/)
|
||||
assert_equal package_names[1], "noexist1"
|
||||
assert package_names[2].match(/\Asecondary_\d+\z/)
|
||||
assert package_names[2].match(/\Asecondary_packaged\z/)
|
||||
assert_equal package_names[3], "noexist2"
|
||||
end
|
||||
|
||||
def test_css_names_from_sources
|
||||
package_names = Synthesis::AssetPackage.targets_from_sources("stylesheets", ["header", "screen", "noexist1", "foo", "noexist2"])
|
||||
assert_equal 4, package_names.length
|
||||
assert package_names[0].match(/\Abase_\d+\z/)
|
||||
assert package_names[0].match(/\Abase_packaged\z/)
|
||||
assert_equal package_names[1], "noexist1"
|
||||
assert package_names[2].match(/\Asecondary_\d+\z/)
|
||||
assert package_names[2].match(/\Asecondary_packaged\z/)
|
||||
assert_equal package_names[3], "noexist2"
|
||||
end
|
||||
|
||||
|
||||
0
vendor/plugins/asset_packager/test/assets/javascripts/application.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/application.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/bar.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/bar.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/controls.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/controls.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/dragdrop.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/dragdrop.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/effects.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/effects.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/foo.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/foo.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/prototype.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/javascripts/prototype.js
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/stylesheets/header.css
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/stylesheets/header.css
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/stylesheets/screen.css
vendored
Normal file → Executable file
0
vendor/plugins/asset_packager/test/assets/stylesheets/screen.css
vendored
Normal file → Executable file
9
vendor/plugins/gemsonrails/init.rb
vendored
9
vendor/plugins/gemsonrails/init.rb
vendored
@@ -1,9 +0,0 @@
|
||||
gems = Dir[File.join(RAILS_ROOT, "vendor/gems/*")]
|
||||
if gems.any?
|
||||
gems.each do |dir|
|
||||
lib = File.join(dir, 'lib')
|
||||
$LOAD_PATH.unshift(lib) if File.directory?(lib)
|
||||
init_rb = File.join(dir, 'init.rb')
|
||||
require init_rb if File.file?(init_rb)
|
||||
end
|
||||
end
|
||||
@@ -1,64 +0,0 @@
|
||||
namespace :gems do
|
||||
desc "Freeze a RubyGem into this Rails application; init.rb will be loaded on startup."
|
||||
task :freeze do
|
||||
unless gem_name = ENV['GEM']
|
||||
puts <<-eos
|
||||
Parameters:
|
||||
GEM Name of gem (required)
|
||||
VERSION Version of gem to freeze (optional)
|
||||
ONLY RAILS_ENVs for which the GEM will be active (optional)
|
||||
|
||||
eos
|
||||
break
|
||||
end
|
||||
|
||||
# ONLY=development[,test] etc
|
||||
only_list = (ENV['ONLY'] || "").split(',')
|
||||
only_if_begin = only_list.size == 0 ? "" : <<-EOS
|
||||
ENV['RAILS_ENV'] ||= 'development'
|
||||
if %w[#{only_list.join(' ')}].include?(ENV['RAILS_ENV'])
|
||||
EOS
|
||||
only_if_end = only_list.size == 0 ? "" : "end"
|
||||
|
||||
require 'rubygems'
|
||||
Gem.manage_gems
|
||||
Gem::CommandManager.new
|
||||
|
||||
gem = (version = ENV['VERSION']) ?
|
||||
Gem.cache.search(gem_name, "= #{version}").first :
|
||||
Gem.cache.search(gem_name).sort_by { |g| g.version }.last
|
||||
|
||||
version ||= gem.version.version rescue nil
|
||||
|
||||
unpack_command_class = Gem::UnpackCommand rescue nil || Gem::Commands::UnpackCommand
|
||||
unless gem && path = unpack_command_class.new.get_path(gem_name, version)
|
||||
raise "No gem #{gem_name} #{version} is installed. Do 'gem list #{gem_name}' to see what you have available."
|
||||
end
|
||||
|
||||
gems_dir = File.join(RAILS_ROOT, 'vendor', 'gems')
|
||||
mkdir_p gems_dir, :verbose => false if !File.exists?(gems_dir)
|
||||
|
||||
target_dir = ENV['TO'] || File.basename(path).sub(/\.gem$/, '')
|
||||
mkdir_p "vendor/gems/#{target_dir}", :verbose => false
|
||||
|
||||
chdir gems_dir, :verbose => false do
|
||||
mkdir_p target_dir, :verbose => false
|
||||
abs_target_dir = File.join(Dir.pwd, target_dir)
|
||||
|
||||
(gem = Gem::Installer.new(path)).unpack(abs_target_dir)
|
||||
chdir target_dir, :verbose => false do
|
||||
if !File.exists?('init.rb')
|
||||
File.open('init.rb', 'w') do |file|
|
||||
file << <<-eos
|
||||
#{only_if_begin}
|
||||
require File.join(File.dirname(__FILE__), 'lib', '#{gem_name}')
|
||||
#{only_if_end}
|
||||
eos
|
||||
end
|
||||
end
|
||||
end
|
||||
puts "Unpacked #{gem_name} #{version} to '#{target_dir}'"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
79
vendor/plugins/gemsonrails/tasks/gems_link.rake
vendored
79
vendor/plugins/gemsonrails/tasks/gems_link.rake
vendored
@@ -1,79 +0,0 @@
|
||||
namespace :gems do
|
||||
desc "Link a RubyGem into this Rails application; init.rb will be loaded on startup."
|
||||
task :link do
|
||||
unless gem_name = ENV['GEM']
|
||||
puts <<-eos
|
||||
Parameters:
|
||||
GEM Name of gem (required)
|
||||
ONLY RAILS_ENVs for which the GEM will be active (optional)
|
||||
|
||||
eos
|
||||
break
|
||||
end
|
||||
|
||||
# ONLY=development[,test] etc
|
||||
only_list = (ENV['ONLY'] || "").split(',')
|
||||
only_if_begin = only_list.size == 0 ? "" : <<-EOS
|
||||
ENV['RAILS_ENV'] ||= 'development'
|
||||
if %w[#{only_list.join(' ')}].include?(ENV['RAILS_ENV'])
|
||||
EOS
|
||||
only_if_end = only_list.size == 0 ? "" : "end"
|
||||
|
||||
require 'rubygems'
|
||||
Gem.manage_gems
|
||||
|
||||
gem = Gem.cache.search(gem_name).sort_by { |g| g.version }.last
|
||||
version ||= gem.version.version rescue nil
|
||||
|
||||
unpack_command_class = Gem::UnpackCommand rescue nil || Gem::Commands::UnpackCommand
|
||||
unless gem && path = unpack_command_class.new.get_path(gem_name, version)
|
||||
raise "No gem #{gem_name} is installed. Try 'gem install #{gem_name}' to install the gem."
|
||||
end
|
||||
|
||||
gems_dir = File.join(RAILS_ROOT, 'vendor', 'gems')
|
||||
mkdir_p gems_dir, :verbose => false if !File.exists?(gems_dir)
|
||||
|
||||
target_dir = ENV['TO'] || gem.name
|
||||
mkdir_p "vendor/gems/#{target_dir}"
|
||||
|
||||
chdir gems_dir, :verbose => false do
|
||||
mkdir_p target_dir + '/tasks', :verbose => false
|
||||
chdir target_dir, :verbose => false do
|
||||
File.open('init.rb', 'w') do |file|
|
||||
file << <<-eos
|
||||
#{only_if_begin}
|
||||
require 'rubygems'
|
||||
Gem.manage_gems
|
||||
gem = Gem.cache.search('#{gem.name}').sort_by { |g| g.version }.last
|
||||
if gem.autorequire
|
||||
require gem.autorequire
|
||||
else
|
||||
require '#{gem.name}'
|
||||
end
|
||||
#{only_if_end}
|
||||
eos
|
||||
end
|
||||
File.open(File.join('tasks', 'load_tasks.rake'), 'w') do |file|
|
||||
file << <<-eos
|
||||
# This file does not include any Rake files, but loads up the
|
||||
# tasks in the /vendor/gems/ folders
|
||||
#{only_if_begin}
|
||||
require 'rubygems'
|
||||
Gem.manage_gems
|
||||
gem = Gem.cache.search('#{gem.name}').sort_by { |g| g.version }.last
|
||||
raise \"Gem '#{gem.name}' is not installed\" if gem.nil?
|
||||
path = gem.full_gem_path
|
||||
Dir[File.join(path, "/**/tasks/**/*.rake")].sort.each { |ext| load ext }
|
||||
#{only_if_end}
|
||||
eos
|
||||
end
|
||||
puts "Linked #{gem_name} (currently #{version}) via 'vendor/gems/#{target_dir}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
task :unfreeze do
|
||||
raise "No gem specified" unless gem_name = ENV['GEM']
|
||||
Dir["vendor/gems/#{gem_name}-*"].each { |d| rm_rf d }
|
||||
end
|
||||
end
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace :gems do
|
||||
desc "Unfreeze/unlink a RubyGem from this Rails application"
|
||||
task :unfreeze do
|
||||
unless gem_name = ENV['GEM']
|
||||
puts <<-eos
|
||||
Parameters:
|
||||
GEM Name of gem (required)
|
||||
|
||||
|
||||
eos
|
||||
break
|
||||
end
|
||||
Dir["vendor/gems/#{gem_name}*"].each { |d| rm_rf d }
|
||||
end
|
||||
end
|
||||
@@ -1,10 +0,0 @@
|
||||
# This file does not include any Rake files, but loads up the
|
||||
# tasks in the /vendor/gems/ folders
|
||||
|
||||
Dir[File.join(RAILS_ROOT, "vendor/gems/*/**/tasks/**/*.rake")].sort.each do |ext|
|
||||
begin
|
||||
load ext
|
||||
rescue
|
||||
puts $!
|
||||
end
|
||||
end
|
||||
14
vendor/plugins/haml/init.rb
vendored
14
vendor/plugins/haml/init.rb
vendored
@@ -1,8 +1,8 @@
|
||||
require 'rubygems'
|
||||
require 'haml'
|
||||
require 'haml/template'
|
||||
require 'sass'
|
||||
require 'sass/plugin'
|
||||
begin
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'haml') # From here
|
||||
rescue LoadError
|
||||
require 'haml' # From gem
|
||||
end
|
||||
|
||||
ActionView::Base.register_template_handler('haml', Haml::Template)
|
||||
Sass::Plugin.update_stylesheets
|
||||
# Load Haml and Sass
|
||||
Haml.init_rails(binding)
|
||||
|
||||
22
vendor/plugins/shoulda/MIT-LICENSE
vendored
22
vendor/plugins/shoulda/MIT-LICENSE
vendored
@@ -1,22 +0,0 @@
|
||||
Copyright (c) 2007, Tammer Saleh, Thoughtbot, Inc.
|
||||
|
||||
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.
|
||||
123
vendor/plugins/shoulda/README
vendored
123
vendor/plugins/shoulda/README
vendored
@@ -1,123 +0,0 @@
|
||||
= Shoulda - Making tests easy on the fingers and eyes
|
||||
|
||||
Shoulda makes it easy to write elegant, understandable, and maintainable tests. Shoulda consists of test macros, assertions, and helpers added on to the Test::Unit framework. It's fully compatible with your existing tests, and requires no retooling to use.
|
||||
|
||||
Helpers:: #context and #should give you rSpec like test blocks.
|
||||
In addition, you get nested contexts and a much more readable syntax.
|
||||
Macros:: Generate hundreds of lines of Controller and ActiveRecord tests with these powerful macros.
|
||||
They get you started quickly, and can help you ensure that your application is conforming to best practices.
|
||||
Assertions:: Many common rails testing idioms have been distilled into a set of useful assertions.
|
||||
|
||||
= Usage
|
||||
|
||||
=== Context Helpers (ThoughtBot::Shoulda::Context)
|
||||
|
||||
Stop killing your fingers with all of those underscores... Name your tests with plain sentences!
|
||||
|
||||
class UserTest << Test::Unit::TestCase
|
||||
context "A User instance" do
|
||||
setup do
|
||||
@user = User.find(:first)
|
||||
end
|
||||
|
||||
should "return its full name"
|
||||
assert_equal 'John Doe', @user.full_name
|
||||
end
|
||||
|
||||
context "with a profile" do
|
||||
setup do
|
||||
@user.profile = Profile.find(:first)
|
||||
end
|
||||
|
||||
should "return true when sent #has_profile?"
|
||||
assert @user.has_profile?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Produces the following test methods:
|
||||
|
||||
"test: A User instance should return its full name."
|
||||
"test: A User instance with a profile should return true when sent #has_profile?."
|
||||
|
||||
So readable!
|
||||
|
||||
=== ActiveRecord Tests (ThoughtBot::Shoulda::ActiveRecord)
|
||||
|
||||
Quick macro tests for your ActiveRecord associations and validations:
|
||||
|
||||
class PostTest < Test::Unit::TestCase
|
||||
load_all_fixtures
|
||||
|
||||
should_belong_to :user
|
||||
should_have_many :tags, :through => :taggings
|
||||
|
||||
should_require_unique_attributes :title
|
||||
should_require_attributes :body, :message => /wtf/
|
||||
should_require_attributes :title
|
||||
should_only_allow_numeric_values_for :user_id
|
||||
end
|
||||
|
||||
class UserTest < Test::Unit::TestCase
|
||||
load_all_fixtures
|
||||
|
||||
should_have_many :posts
|
||||
|
||||
should_not_allow_values_for :email, "blah", "b lah"
|
||||
should_allow_values_for :email, "a@b.com", "asdf@asdf.com"
|
||||
should_ensure_length_in_range :email, 1..100
|
||||
should_ensure_value_in_range :age, 1..100
|
||||
should_protect_attributes :password
|
||||
end
|
||||
|
||||
Makes TDD so much easier.
|
||||
|
||||
=== Controller Tests (ThoughtBot::Shoulda::Controller::ClassMethods)
|
||||
|
||||
Macros to test the most common controller patterns...
|
||||
|
||||
context "on GET to :show for first record" do
|
||||
setup do
|
||||
get :show, :id => 1
|
||||
end
|
||||
|
||||
should_assign_to :user
|
||||
should_respond_with :success
|
||||
should_render_template :show
|
||||
should_not_set_the_flash
|
||||
|
||||
should "do something else really cool" do
|
||||
assert_equal 1, assigns(:user).id
|
||||
end
|
||||
end
|
||||
|
||||
Test entire controllers in a few lines...
|
||||
|
||||
class PostsControllerTest < Test::Unit::TestCase
|
||||
should_be_restful do |resource|
|
||||
resource.parent = :user
|
||||
|
||||
resource.create.params = { :title => "first post", :body => 'blah blah blah'}
|
||||
resource.update.params = { :title => "changed" }
|
||||
end
|
||||
end
|
||||
|
||||
should_be_restful generates 40 tests on the fly, for both html and xml requests.
|
||||
|
||||
=== Helpful Assertions (ThoughtBot::Shoulda::General)
|
||||
|
||||
More to come here, but have fun with what's there.
|
||||
|
||||
load_all_fixtures
|
||||
assert_same_elements([:a, :b, :c], [:c, :a, :b])
|
||||
assert_contains(['a', '1'], /\d/)
|
||||
assert_contains(['a', '1'], 'a')
|
||||
|
||||
= Credits
|
||||
|
||||
Shoulda is maintained by {Tammer Saleh}[mailto:tsaleh@thoughtbot.com], and is funded by Thoughtbot[http://www.thoughtbot.com], inc.
|
||||
|
||||
= License
|
||||
|
||||
Shoulda is Copyright © 2006-2007 Tammer Saleh, Thoughtbot. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
|
||||
32
vendor/plugins/shoulda/Rakefile
vendored
32
vendor/plugins/shoulda/Rakefile
vendored
@@ -1,32 +0,0 @@
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
# Test::Unit::UI::VERBOSE
|
||||
|
||||
Rake::TestTask.new do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/{unit,functional,other}/**/*_test.rb'
|
||||
t.verbose = false
|
||||
end
|
||||
|
||||
Rake::RDocTask.new { |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = "Shoulda -- Making tests easy on the fingers and eyes"
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
||||
rdoc.rdoc_files.include('README', 'lib/**/*.rb')
|
||||
}
|
||||
|
||||
desc 'Update documentation on website'
|
||||
task :sync_docs => 'rdoc' do
|
||||
`rsync -ave ssh doc/ dev@dev.thoughtbot.com:/home/dev/www/dev.thoughtbot.com/shoulda`
|
||||
end
|
||||
|
||||
desc 'Default: run tests.'
|
||||
task :default => ['test']
|
||||
|
||||
Dir['tasks/*.rake'].each do |f|
|
||||
load f
|
||||
end
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
require 'fileutils'
|
||||
|
||||
def usage(msg = nil)
|
||||
puts "Error: #{msg}" if msg
|
||||
puts if msg
|
||||
puts "Usage: #{File.basename(__FILE__)} normal_test_file.rb"
|
||||
puts
|
||||
puts "Will convert an existing test file with names like "
|
||||
puts
|
||||
puts " def test_should_do_stuff"
|
||||
puts " ..."
|
||||
puts " end"
|
||||
puts
|
||||
puts "to one using the new syntax: "
|
||||
puts
|
||||
puts " should \"be super cool\" do"
|
||||
puts " ..."
|
||||
puts " end"
|
||||
puts
|
||||
puts "A copy of the old file will be left under /tmp/ in case this script just seriously screws up"
|
||||
puts
|
||||
exit (msg ? 2 : 0)
|
||||
end
|
||||
|
||||
usage("Wrong number of arguments.") unless ARGV.size == 1
|
||||
usage("This system doesn't have a /tmp directory. wtf?") unless File.directory?('/tmp')
|
||||
|
||||
file = ARGV.shift
|
||||
tmpfile = "/tmp/#{File.basename(file)}"
|
||||
usage("File '#{file}' doesn't exist") unless File.exists?(file)
|
||||
|
||||
FileUtils.cp(file, tmpfile)
|
||||
contents = File.read(tmpfile)
|
||||
contents.gsub!(/def test_should_(.*)\s*$/, 'should "\1" do')
|
||||
contents.gsub!(/def test_(.*)\s*$/, 'should "RENAME ME: test \1" do')
|
||||
contents.gsub!(/should ".*" do$/) {|line| line.tr!('_', ' ')}
|
||||
File.open(file, 'w') { |f| f.write(contents) }
|
||||
|
||||
puts "File '#{file}' has been converted to 'should' syntax. Old version has been stored in '#{tmpfile}'"
|
||||
1
vendor/plugins/shoulda/cruise_control.rb
vendored
1
vendor/plugins/shoulda/cruise_control.rb
vendored
@@ -1 +0,0 @@
|
||||
/home/builder/public_repo_cruise_config.rb
|
||||
3
vendor/plugins/shoulda/init.rb
vendored
3
vendor/plugins/shoulda/init.rb
vendored
@@ -1,3 +0,0 @@
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
require 'shoulda'
|
||||
45
vendor/plugins/shoulda/lib/shoulda.rb
vendored
45
vendor/plugins/shoulda/lib/shoulda.rb
vendored
@@ -1,45 +0,0 @@
|
||||
require 'yaml'
|
||||
require 'shoulda/private_helpers'
|
||||
require 'shoulda/general'
|
||||
require 'shoulda/gem/shoulda'
|
||||
require 'shoulda/active_record_helpers'
|
||||
require 'shoulda/controller_tests/controller_tests.rb'
|
||||
|
||||
shoulda_options = {}
|
||||
|
||||
possible_config_paths = []
|
||||
possible_config_paths << File.join(ENV["HOME"], ".shoulda.conf") if ENV["HOME"]
|
||||
possible_config_paths << "shoulda.conf"
|
||||
possible_config_paths << File.join("test", "shoulda.conf")
|
||||
possible_config_paths << File.join(RAILS_ROOT, "test", "shoulda.conf") if defined?(RAILS_ROOT)
|
||||
|
||||
possible_config_paths.each do |config_file|
|
||||
if File.exists? config_file
|
||||
shoulda_options = YAML.load_file(config_file).symbolize_keys
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
require 'shoulda/color' if shoulda_options[:color]
|
||||
|
||||
module Test # :nodoc: all
|
||||
module Unit
|
||||
class TestCase
|
||||
|
||||
include ThoughtBot::Shoulda::Controller
|
||||
include ThoughtBot::Shoulda::General
|
||||
|
||||
class << self
|
||||
include ThoughtBot::Shoulda::ActiveRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActionController #:nodoc: all
|
||||
module Integration
|
||||
class Session
|
||||
include ThoughtBot::Shoulda::General
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,446 +0,0 @@
|
||||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
# = Macro test helpers for your active record models
|
||||
#
|
||||
# These helpers will test most of the validations and associations for your ActiveRecord models.
|
||||
#
|
||||
# class UserTest < Test::Unit::TestCase
|
||||
# should_require_attributes :name, :phone_number
|
||||
# should_not_allow_values_for :phone_number, "abcd", "1234"
|
||||
# should_allow_values_for :phone_number, "(123) 456-7890"
|
||||
#
|
||||
# should_protect_attributes :password
|
||||
#
|
||||
# should_have_one :profile
|
||||
# should_have_many :dogs
|
||||
# should_have_many :messes, :through => :dogs
|
||||
# should_belong_to :lover
|
||||
# end
|
||||
#
|
||||
# For all of these helpers, the last parameter may be a hash of options.
|
||||
#
|
||||
module ActiveRecord
|
||||
# Ensures that the model cannot be saved if one of the attributes listed is not present.
|
||||
# Requires an existing record.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>/blank/</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_require_attributes :name, :phone_number
|
||||
#
|
||||
def should_require_attributes(*attributes)
|
||||
message = get_options!(attributes, :message)
|
||||
message ||= /blank/
|
||||
klass = model_class
|
||||
|
||||
attributes.each do |attribute|
|
||||
should "require #{attribute} to be set" do
|
||||
object = klass.new
|
||||
assert !object.valid?, "#{klass.name} does not require #{attribute}."
|
||||
assert object.errors.on(attribute), "#{klass.name} does not require #{attribute}."
|
||||
assert_contains(object.errors.on(attribute), message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the model cannot be saved if one of the attributes listed is not unique.
|
||||
# Requires an existing record
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>/taken/</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_require_unique_attributes :keyword, :username
|
||||
#
|
||||
def should_require_unique_attributes(*attributes)
|
||||
message, scope = get_options!(attributes, :message, :scoped_to)
|
||||
message ||= /taken/
|
||||
|
||||
klass = model_class
|
||||
attributes.each do |attribute|
|
||||
attribute = attribute.to_sym
|
||||
should "require unique value for #{attribute}#{" scoped to #{scope}" if scope}" do
|
||||
assert existing = klass.find(:first), "Can't find first #{klass}"
|
||||
object = klass.new
|
||||
|
||||
object.send(:"#{attribute}=", existing.send(attribute))
|
||||
if scope
|
||||
assert_respond_to object, :"#{scope}=", "#{klass.name} doesn't seem to have a #{scope} attribute."
|
||||
object.send(:"#{scope}=", existing.send(scope))
|
||||
end
|
||||
|
||||
assert !object.valid?, "#{klass.name} does not require a unique value for #{attribute}."
|
||||
assert object.errors.on(attribute), "#{klass.name} does not require a unique value for #{attribute}."
|
||||
|
||||
assert_contains(object.errors.on(attribute), message)
|
||||
|
||||
if scope
|
||||
# Now test that the object is valid when changing the scoped attribute
|
||||
# TODO: actually find all values for scope and create a unique one.
|
||||
object.send(:"#{scope}=", existing.send(scope).nil? ? 1 : existing.send(scope).next)
|
||||
object.errors.clear
|
||||
object.valid?
|
||||
assert_does_not_contain(object.errors.on(attribute), message,
|
||||
"after :#{scope} set to #{object.send(scope.to_sym)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the attribute cannot be set on update
|
||||
# Requires an existing record
|
||||
#
|
||||
# should_protect_attributes :password, :admin_flag
|
||||
#
|
||||
def should_protect_attributes(*attributes)
|
||||
get_options!(attributes)
|
||||
klass = model_class
|
||||
attributes.each do |attribute|
|
||||
attribute = attribute.to_sym
|
||||
should "not allow #{attribute} to be changed by update" do
|
||||
assert object = klass.find(:first), "Can't find first #{klass}"
|
||||
value = object[attribute]
|
||||
# TODO: 1 may not be a valid value for the attribute (due to validations)
|
||||
assert object.update_attributes({ attribute => 1 }),
|
||||
"Cannot update #{klass} with { :#{attribute} => 1 }, #{object.errors.full_messages.to_sentence}"
|
||||
assert object.valid?, "#{klass} isn't valid after changing #{attribute}"
|
||||
assert_equal value, object[attribute], "Was able to change #{klass}##{attribute}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the attribute cannot be set to the given values
|
||||
# Requires an existing record
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>/invalid/</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_not_allow_values_for :isbn, "bad 1", "bad 2"
|
||||
#
|
||||
def should_not_allow_values_for(attribute, *bad_values)
|
||||
message = get_options!(bad_values, :message)
|
||||
message ||= /invalid/
|
||||
klass = model_class
|
||||
bad_values.each do |v|
|
||||
should "not allow #{attribute} to be set to \"#{v}\"" do
|
||||
assert object = klass.find(:first), "Can't find first #{klass}"
|
||||
object.send("#{attribute}=", v)
|
||||
assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
|
||||
assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
|
||||
assert_contains(object.errors.on(attribute), message, "when set to \"#{v}\"")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the attribute can be set to the given values.
|
||||
# Requires an existing record
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>/invalid/</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
|
||||
#
|
||||
def should_allow_values_for(attribute, *good_values)
|
||||
message = get_options!(good_values, :message)
|
||||
message ||= /invalid/
|
||||
klass = model_class
|
||||
good_values.each do |v|
|
||||
should "allow #{attribute} to be set to \"#{v}\"" do
|
||||
assert object = klass.find(:first), "Can't find first #{klass}"
|
||||
object.send("#{attribute}=", v)
|
||||
object.save
|
||||
assert_does_not_contain(object.errors.on(attribute), message, "when set to \"#{v}\"")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the length of the attribute is in the given range
|
||||
# Requires an existing record
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>/short/</tt>
|
||||
# * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>/long/</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_ensure_length_in_range :password, (6..20)
|
||||
#
|
||||
def should_ensure_length_in_range(attribute, range, opts = {})
|
||||
short_message, long_message = get_options!([opts], :short_message, :long_message)
|
||||
short_message ||= /short/
|
||||
long_message ||= /long/
|
||||
|
||||
klass = model_class
|
||||
min_length = range.first
|
||||
max_length = range.last
|
||||
|
||||
if min_length > 0
|
||||
min_value = "x" * (min_length - 1)
|
||||
should "not allow #{attribute} to be less than #{min_length} chars long" do
|
||||
assert object = klass.find(:first), "Can't find first #{klass}"
|
||||
object.send("#{attribute}=", min_value)
|
||||
assert !object.save, "Saved #{klass} with #{attribute} set to \"#{min_value}\""
|
||||
assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{min_value}\""
|
||||
assert_contains(object.errors.on(attribute), short_message, "when set to \"#{min_value}\"")
|
||||
end
|
||||
end
|
||||
|
||||
max_value = "x" * (max_length + 1)
|
||||
should "not allow #{attribute} to be more than #{max_length} chars long" do
|
||||
assert object = klass.find(:first), "Can't find first #{klass}"
|
||||
object.send("#{attribute}=", max_value)
|
||||
assert !object.save, "Saved #{klass} with #{attribute} set to \"#{max_value}\""
|
||||
assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{max_value}\""
|
||||
assert_contains(object.errors.on(attribute), long_message, "when set to \"#{max_value}\"")
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the attribute is in the range specified
|
||||
# Requires an existing record
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>/included/</tt>
|
||||
# * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>/included/</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_ensure_value_in_range :age, (0..100)
|
||||
#
|
||||
def should_ensure_value_in_range(attribute, range, opts = {})
|
||||
low_message, high_message = get_options!([opts], :low_message, :high_message)
|
||||
low_message ||= /included/
|
||||
high_message ||= /included/
|
||||
|
||||
klass = model_class
|
||||
min = range.first
|
||||
max = range.last
|
||||
|
||||
should "not allow #{attribute} to be less than #{min}" do
|
||||
v = min - 1
|
||||
assert object = klass.find(:first), "Can't find first #{klass}"
|
||||
object.send("#{attribute}=", v)
|
||||
assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
|
||||
assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
|
||||
assert_contains(object.errors.on(attribute), low_message, "when set to \"#{v}\"")
|
||||
end
|
||||
|
||||
should "not allow #{attribute} to be more than #{max}" do
|
||||
v = max + 1
|
||||
assert object = klass.find(:first), "Can't find first #{klass}"
|
||||
object.send("#{attribute}=", v)
|
||||
assert !object.save, "Saved #{klass} with #{attribute} set to \"#{v}\""
|
||||
assert object.errors.on(attribute), "There are no errors set on #{attribute} after being set to \"#{v}\""
|
||||
assert_contains(object.errors.on(attribute), high_message, "when set to \"#{v}\"")
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the attribute is numeric
|
||||
# Requires an existing record
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
||||
# Regexp or string. Default = <tt>/number/</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_only_allow_numeric_values_for :age
|
||||
#
|
||||
def should_only_allow_numeric_values_for(*attributes)
|
||||
message = get_options!(attributes, :message)
|
||||
message ||= /number/
|
||||
klass = model_class
|
||||
attributes.each do |attribute|
|
||||
attribute = attribute.to_sym
|
||||
should "only allow numeric values for #{attribute}" do
|
||||
assert object = klass.find(:first), "Can't find first #{klass}"
|
||||
object.send(:"#{attribute}=", "abcd")
|
||||
assert !object.valid?, "Instance is still valid"
|
||||
assert_contains(object.errors.on(attribute), message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the has_many relationship exists.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:through</tt> - association name for <tt>has_many :through</tt>
|
||||
#
|
||||
# Example:
|
||||
# should_have_many :friends
|
||||
# should_have_many :enemies, :through => :friends
|
||||
#
|
||||
def should_have_many(*associations)
|
||||
through = get_options!(associations, :through)
|
||||
klass = model_class
|
||||
associations.each do |association|
|
||||
name = "have many #{association}"
|
||||
name += " through #{through}" if through
|
||||
should name do
|
||||
reflection = klass.reflect_on_association(association)
|
||||
assert reflection, "#{klass.name} does not have any relationship to #{association}"
|
||||
assert_equal :has_many, reflection.macro
|
||||
|
||||
if through
|
||||
through_reflection = klass.reflect_on_association(through)
|
||||
assert through_reflection, "#{klass.name} does not have any relationship to #{through}"
|
||||
assert_equal(through, reflection.options[:through])
|
||||
end
|
||||
|
||||
unless reflection.options[:through]
|
||||
# This is not a through association, so check for the existence of the foreign key on the other table
|
||||
if reflection.options[:foreign_key]
|
||||
fk = reflection.options[:foreign_key]
|
||||
elsif reflection.options[:as]
|
||||
fk = reflection.options[:as].to_s.foreign_key
|
||||
else
|
||||
fk = klass.name.foreign_key
|
||||
end
|
||||
associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize
|
||||
assert associated_klass.column_names.include?(fk.to_s), "#{associated_klass.name} does not have a #{fk} foreign key."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensures that the has_and_belongs_to_many relationship exists.
|
||||
#
|
||||
# should_have_and_belong_to_many :posts, :cars
|
||||
#
|
||||
def should_have_and_belong_to_many(*associations)
|
||||
get_options!(associations)
|
||||
klass = model_class
|
||||
associations.each do |association|
|
||||
should "should have and belong to many #{association}" do
|
||||
assert klass.reflect_on_association(association), "#{klass.name} does not have any relationship to #{association}"
|
||||
assert_equal :has_and_belongs_to_many, klass.reflect_on_association(association).macro
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the has_one relationship exists.
|
||||
#
|
||||
# should_have_one :god # unless hindu
|
||||
#
|
||||
def should_have_one(*associations)
|
||||
get_options!(associations)
|
||||
klass = model_class
|
||||
associations.each do |association|
|
||||
should "have one #{association}" do
|
||||
reflection = klass.reflect_on_association(association)
|
||||
assert reflection, "#{klass.name} does not have any relationship to #{association}"
|
||||
assert_equal :has_one, reflection.macro
|
||||
|
||||
if reflection.options[:foreign_key]
|
||||
fk = reflection.options[:foreign_key]
|
||||
elsif reflection.options[:as]
|
||||
fk = reflection.options[:as].to_s.foreign_key
|
||||
else
|
||||
fk = klass.name.foreign_key
|
||||
end
|
||||
associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize
|
||||
assert associated_klass.column_names.include?(fk.to_s), "#{associated_klass.name} does not have a #{fk} foreign key."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the belongs_to relationship exists.
|
||||
#
|
||||
# should_belong_to :parent
|
||||
#
|
||||
def should_belong_to(*associations)
|
||||
get_options!(associations)
|
||||
klass = model_class
|
||||
associations.each do |association|
|
||||
should "belong_to #{association}" do
|
||||
reflection = klass.reflect_on_association(association)
|
||||
assert reflection, "#{klass.name} does not have any relationship to #{association}"
|
||||
assert_equal :belongs_to, reflection.macro
|
||||
|
||||
unless reflection.options[:polymorphic]
|
||||
associated_klass = (reflection.options[:class_name] || association.to_s.classify).constantize
|
||||
fk = reflection.options[:foreign_key] || associated_klass.name.foreign_key
|
||||
assert klass.column_names.include?(fk.to_s), "#{klass.name} does not have a #{fk} foreign key."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the given class methods are defined on the model.
|
||||
#
|
||||
# should_have_class_methods :find, :destroy
|
||||
#
|
||||
def should_have_class_methods(*methods)
|
||||
get_options!(methods)
|
||||
klass = model_class
|
||||
methods.each do |method|
|
||||
should "respond to class method #{method}" do
|
||||
assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the given instance methods are defined on the model.
|
||||
#
|
||||
# should_have_instance_methods :email, :name, :name=
|
||||
#
|
||||
def should_have_instance_methods(*methods)
|
||||
get_options!(methods)
|
||||
klass = model_class
|
||||
methods.each do |method|
|
||||
should "respond to instance method #{method}" do
|
||||
assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the given columns are defined on the models backing SQL table.
|
||||
#
|
||||
# should_have_db_columns :id, :email, :name, :created_at
|
||||
#
|
||||
def should_have_db_columns(*columns)
|
||||
column_type = get_options!(columns, :type)
|
||||
klass = model_class
|
||||
columns.each do |name|
|
||||
test_name = "have column #{name}"
|
||||
test_name += " of type #{column_type}" if column_type
|
||||
should test_name do
|
||||
column = klass.columns.detect {|c| c.name == name.to_s }
|
||||
assert column, "#{klass.name} does not have column #{name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure that the given column is defined on the models backing SQL table. The options are the same as
|
||||
# the instance variables defined on the column definition: :precision, :limit, :default, :null,
|
||||
# :primary, :type, :scale, and :sql_type.
|
||||
#
|
||||
# should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255,
|
||||
# :null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)'
|
||||
#
|
||||
def should_have_db_column(name, opts = {})
|
||||
klass = model_class
|
||||
test_name = "have column named :#{name}"
|
||||
test_name += " with options " + opts.inspect unless opts.empty?
|
||||
should test_name do
|
||||
column = klass.columns.detect {|c| c.name == name.to_s }
|
||||
assert column, "#{klass.name} does not have column #{name}"
|
||||
opts.each do |k, v|
|
||||
assert_equal column.instance_variable_get("@#{k}").to_s, v.to_s, ":#{name} column on table for #{klass} does not match option :#{k}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
include ThoughtBot::Shoulda::Private
|
||||
end
|
||||
end
|
||||
end
|
||||
77
vendor/plugins/shoulda/lib/shoulda/color.rb
vendored
77
vendor/plugins/shoulda/lib/shoulda/color.rb
vendored
@@ -1,77 +0,0 @@
|
||||
require 'test/unit/ui/console/testrunner'
|
||||
|
||||
# Completely stolen from redgreen gem
|
||||
#
|
||||
# Adds colored output to your tests. Specify <tt>color: true</tt> in
|
||||
# your <tt>~/.shoulda.conf</tt> file to enable.
|
||||
#
|
||||
# *Bug*: for some reason, this adds another line of output to the end of
|
||||
# every rake task, as though there was another (empty) set of tests.
|
||||
# A fix would be most welcome.
|
||||
#
|
||||
module ThoughtBot::Shoulda::Color
|
||||
COLORS = { :clear => 0, :red => 31, :green => 32, :yellow => 33 } # :nodoc:
|
||||
def self.method_missing(color_name, *args) # :nodoc:
|
||||
color(color_name) + args.first + color(:clear)
|
||||
end
|
||||
def self.color(color) # :nodoc:
|
||||
"\e[#{COLORS[color.to_sym]}m"
|
||||
end
|
||||
end
|
||||
|
||||
module Test # :nodoc:
|
||||
module Unit # :nodoc:
|
||||
class TestResult # :nodoc:
|
||||
alias :old_to_s :to_s
|
||||
def to_s
|
||||
if old_to_s =~ /\d+ tests, \d+ assertions, (\d+) failures, (\d+) errors/
|
||||
ThoughtBot::Shoulda::Color.send($1.to_i != 0 || $2.to_i != 0 ? :red : :green, $&)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class AutoRunner # :nodoc:
|
||||
alias :old_initialize :initialize
|
||||
def initialize(standalone)
|
||||
old_initialize(standalone)
|
||||
@runner = proc do |r|
|
||||
Test::Unit::UI::Console::RedGreenTestRunner
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Failure # :nodoc:
|
||||
alias :old_long_display :long_display
|
||||
def long_display
|
||||
# old_long_display.sub('Failure', ThoughtBot::Shoulda::Color.red('Failure'))
|
||||
ThoughtBot::Shoulda::Color.red(old_long_display)
|
||||
end
|
||||
end
|
||||
|
||||
class Error # :nodoc:
|
||||
alias :old_long_display :long_display
|
||||
def long_display
|
||||
# old_long_display.sub('Error', ThoughtBot::Shoulda::Color.yellow('Error'))
|
||||
ThoughtBot::Shoulda::Color.yellow(old_long_display)
|
||||
end
|
||||
end
|
||||
|
||||
module UI # :nodoc:
|
||||
module Console # :nodoc:
|
||||
class RedGreenTestRunner < Test::Unit::UI::Console::TestRunner # :nodoc:
|
||||
def output_single(something, level=NORMAL)
|
||||
return unless (output?(level))
|
||||
something = case something
|
||||
when '.' then ThoughtBot::Shoulda::Color.green('.')
|
||||
when 'F' then ThoughtBot::Shoulda::Color.red("F")
|
||||
when 'E' then ThoughtBot::Shoulda::Color.yellow("E")
|
||||
else something
|
||||
end
|
||||
@io.write(something)
|
||||
@io.flush
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,465 +0,0 @@
|
||||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
module Controller
|
||||
def self.included(other) # :nodoc:
|
||||
other.class_eval do
|
||||
extend ThoughtBot::Shoulda::Controller::ClassMethods
|
||||
include ThoughtBot::Shoulda::Controller::InstanceMethods
|
||||
ThoughtBot::Shoulda::Controller::ClassMethods::VALID_FORMATS.each do |format|
|
||||
include "ThoughtBot::Shoulda::Controller::#{format.to_s.upcase}".constantize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# = Macro test helpers for your controllers
|
||||
#
|
||||
# By using the macro helpers you can quickly and easily create concise and easy to read test suites.
|
||||
#
|
||||
# This code segment:
|
||||
# context "on GET to :show for first record" do
|
||||
# setup do
|
||||
# get :show, :id => 1
|
||||
# end
|
||||
#
|
||||
# should_assign_to :user
|
||||
# should_respond_with :success
|
||||
# should_render_template :show
|
||||
# should_not_set_the_flash
|
||||
#
|
||||
# should "do something else really cool" do
|
||||
# assert_equal 1, assigns(:user).id
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Would produce 5 tests for the +show+ action
|
||||
#
|
||||
# Furthermore, the should_be_restful helper will create an entire set of tests which will verify that your
|
||||
# controller responds restfully to a variety of requested formats.
|
||||
module ClassMethods
|
||||
# Formats tested by #should_be_restful. Defaults to [:html, :xml]
|
||||
VALID_FORMATS = Dir.glob(File.join(File.dirname(__FILE__), 'formats', '*.rb')).map { |f| File.basename(f, '.rb') }.map(&:to_sym) # :doc:
|
||||
VALID_FORMATS.each {|f| require "shoulda/controller_tests/formats/#{f}.rb"}
|
||||
|
||||
# Actions tested by #should_be_restful
|
||||
VALID_ACTIONS = [:index, :show, :new, :edit, :create, :update, :destroy] # :doc:
|
||||
|
||||
# A ResourceOptions object is passed into should_be_restful in order to configure the tests for your controller.
|
||||
#
|
||||
# Example:
|
||||
# class UsersControllerTest < Test::Unit::TestCase
|
||||
# load_all_fixtures
|
||||
#
|
||||
# def setup
|
||||
# ...normal setup code...
|
||||
# @user = User.find(:first)
|
||||
# end
|
||||
#
|
||||
# should_be_restful do |resource|
|
||||
# resource.identifier = :id
|
||||
# resource.klass = User
|
||||
# resource.object = :user
|
||||
# resource.parent = []
|
||||
# resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy]
|
||||
# resource.formats = [:html, :xml]
|
||||
#
|
||||
# resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13}
|
||||
# resource.update.params = { :name => "sue" }
|
||||
#
|
||||
# resource.create.redirect = "user_url(@user)"
|
||||
# resource.update.redirect = "user_url(@user)"
|
||||
# resource.destroy.redirect = "users_url"
|
||||
#
|
||||
# resource.create.flash = /created/i
|
||||
# resource.update.flash = /updated/i
|
||||
# resource.destroy.flash = /removed/i
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Whenever possible, the resource attributes will be set to sensible defaults.
|
||||
#
|
||||
class ResourceOptions
|
||||
# Configuration options for the create, update, destroy actions under should_be_restful
|
||||
class ActionOptions
|
||||
# String evaled to get the target of the redirection.
|
||||
# All of the instance variables set by the controller will be available to the
|
||||
# evaled code.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.redirect = "user_url(@user.company, @user)"
|
||||
#
|
||||
# Defaults to a generated url based on the name of the controller, the action, and the resource.parents list.
|
||||
attr_accessor :redirect
|
||||
|
||||
# String or Regexp describing a value expected in the flash. Will match against any flash key.
|
||||
#
|
||||
# Defaults:
|
||||
# destroy:: /removed/
|
||||
# create:: /created/
|
||||
# update:: /updated/
|
||||
attr_accessor :flash
|
||||
|
||||
# Hash describing the params that should be sent in with this action.
|
||||
attr_accessor :params
|
||||
end
|
||||
|
||||
# Configuration options for the denied actions under should_be_restful
|
||||
#
|
||||
# Example:
|
||||
# context "The public" do
|
||||
# setup do
|
||||
# @request.session[:logged_in] = false
|
||||
# end
|
||||
#
|
||||
# should_be_restful do |resource|
|
||||
# resource.parent = :user
|
||||
#
|
||||
# resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
|
||||
# resource.denied.flash = /get outta here/i
|
||||
# resource.denied.redirect = 'new_session_url'
|
||||
# end
|
||||
# end
|
||||
#
|
||||
class DeniedOptions
|
||||
# String evaled to get the target of the redirection.
|
||||
# All of the instance variables set by the controller will be available to the
|
||||
# evaled code.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.redirect = "user_url(@user.company, @user)"
|
||||
attr_accessor :redirect
|
||||
|
||||
# String or Regexp describing a value expected in the flash. Will match against any flash key.
|
||||
#
|
||||
# Example:
|
||||
# resource.create.flash = /created/
|
||||
attr_accessor :flash
|
||||
|
||||
# Actions that should be denied (only used by resource.denied). <i>Note that these actions will
|
||||
# only be tested if they are also listed in +resource.actions+</i>
|
||||
# The special value of :all will deny all of the REST actions.
|
||||
attr_accessor :actions
|
||||
end
|
||||
|
||||
# Name of key in params that references the primary key.
|
||||
# Will almost always be :id (default), unless you are using a plugin or have patched rails.
|
||||
attr_accessor :identifier
|
||||
|
||||
# Name of the ActiveRecord class this resource is responsible for. Automatically determined from
|
||||
# test class if not explicitly set. UserTest => :user
|
||||
attr_accessor :klass
|
||||
|
||||
# Name of the instantiated ActiveRecord object that should be used by some of the tests.
|
||||
# Defaults to the underscored name of the AR class. CompanyManager => :company_manager
|
||||
attr_accessor :object
|
||||
|
||||
# Name of the parent AR objects.
|
||||
#
|
||||
# Example:
|
||||
# # in the routes...
|
||||
# map.resources :companies do
|
||||
# map.resources :people do
|
||||
# map.resources :limbs
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # in the tests...
|
||||
# class PeopleControllerTest < Test::Unit::TestCase
|
||||
# should_be_restful do |resource|
|
||||
# resource.parent = :companies
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# class LimbsControllerTest < Test::Unit::TestCase
|
||||
# should_be_restful do |resource|
|
||||
# resource.parents = [:companies, :people]
|
||||
# end
|
||||
# end
|
||||
attr_accessor :parent
|
||||
alias parents parent
|
||||
alias parents= parent=
|
||||
|
||||
# Actions that should be tested. Must be a subset of VALID_ACTIONS (default).
|
||||
# Tests for each actionw will only be generated if the action is listed here.
|
||||
# The special value of :all will test all of the REST actions.
|
||||
#
|
||||
# Example (for a read-only controller):
|
||||
# resource.actions = [:show, :index]
|
||||
attr_accessor :actions
|
||||
|
||||
# Formats that should be tested. Must be a subset of VALID_FORMATS (default).
|
||||
# Each action will be tested against the formats listed here. The special value
|
||||
# of :all will test all of the supported formats.
|
||||
#
|
||||
# Example:
|
||||
# resource.actions = [:html, :xml]
|
||||
attr_accessor :formats
|
||||
|
||||
# ActionOptions object specifying options for the create action.
|
||||
attr_accessor :create
|
||||
|
||||
# ActionOptions object specifying options for the update action.
|
||||
attr_accessor :update
|
||||
|
||||
# ActionOptions object specifying options for the desrtoy action.
|
||||
attr_accessor :destroy
|
||||
|
||||
# DeniedOptions object specifying which actions should return deny a request, and what should happen in that case.
|
||||
attr_accessor :denied
|
||||
|
||||
def initialize # :nodoc:
|
||||
@create = ActionOptions.new
|
||||
@update = ActionOptions.new
|
||||
@destroy = ActionOptions.new
|
||||
@denied = DeniedOptions.new
|
||||
|
||||
@create.flash ||= /created/i
|
||||
@update.flash ||= /updated/i
|
||||
@destroy.flash ||= /removed/i
|
||||
@denied.flash ||= /denied/i
|
||||
|
||||
@create.params ||= {}
|
||||
@update.params ||= {}
|
||||
|
||||
@actions = VALID_ACTIONS
|
||||
@formats = VALID_FORMATS
|
||||
@denied.actions = []
|
||||
end
|
||||
|
||||
def normalize!(target) # :nodoc:
|
||||
@denied.actions = VALID_ACTIONS if @denied.actions == :all
|
||||
@actions = VALID_ACTIONS if @actions == :all
|
||||
@formats = VALID_FORMATS if @formats == :all
|
||||
|
||||
@denied.actions = @denied.actions.map(&:to_sym)
|
||||
@actions = @actions.map(&:to_sym)
|
||||
@formats = @formats.map(&:to_sym)
|
||||
|
||||
ensure_valid_members(@actions, VALID_ACTIONS, 'actions')
|
||||
ensure_valid_members(@denied.actions, VALID_ACTIONS, 'denied.actions')
|
||||
ensure_valid_members(@formats, VALID_FORMATS, 'formats')
|
||||
|
||||
@identifier ||= :id
|
||||
@klass ||= target.name.gsub(/ControllerTest$/, '').singularize.constantize
|
||||
@object ||= @klass.name.tableize.singularize
|
||||
@parent ||= []
|
||||
@parent = [@parent] unless @parent.is_a? Array
|
||||
|
||||
collection_helper = [@parent, @object.pluralize, 'url'].flatten.join('_')
|
||||
collection_args = @parent.map {|n| "@#{object}.#{n}"}.join(', ')
|
||||
@destroy.redirect ||= "#{collection_helper}(#{collection_args})"
|
||||
|
||||
member_helper = [@parent, @object, 'url'].flatten.join('_')
|
||||
member_args = [@parent.map {|n| "@#{object}.#{n}"}, "@#{object}"].flatten.join(', ')
|
||||
@create.redirect ||= "#{member_helper}(#{member_args})"
|
||||
@update.redirect ||= "#{member_helper}(#{member_args})"
|
||||
@denied.redirect ||= "new_session_url"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_valid_members(ary, valid_members, name) # :nodoc:
|
||||
invalid = ary - valid_members
|
||||
raise ArgumentError, "Unsupported #{name}: #{invalid.inspect}" unless invalid.empty?
|
||||
end
|
||||
end
|
||||
|
||||
# :section: should_be_restful
|
||||
# Generates a full suite of tests for a restful controller.
|
||||
#
|
||||
# The following definition will generate tests for the +index+, +show+, +new+,
|
||||
# +edit+, +create+, +update+ and +destroy+ actions, in both +html+ and +xml+ formats:
|
||||
#
|
||||
# should_be_restful do |resource|
|
||||
# resource.parent = :user
|
||||
#
|
||||
# resource.create.params = { :title => "first post", :body => 'blah blah blah'}
|
||||
# resource.update.params = { :title => "changed" }
|
||||
# end
|
||||
#
|
||||
# This generates about 40 tests, all of the format:
|
||||
# "on GET to :show should assign @user."
|
||||
# "on GET to :show should not set the flash."
|
||||
# "on GET to :show should render 'show' template."
|
||||
# "on GET to :show should respond with success."
|
||||
# "on GET to :show as xml should assign @user."
|
||||
# "on GET to :show as xml should have ContentType set to 'application/xml'."
|
||||
# "on GET to :show as xml should respond with success."
|
||||
# "on GET to :show as xml should return <user/> as the root element."
|
||||
# The +resource+ parameter passed into the block is a ResourceOptions object, and
|
||||
# is used to configure the tests for the details of your resources.
|
||||
#
|
||||
def should_be_restful(&blk) # :yields: resource
|
||||
resource = ResourceOptions.new
|
||||
blk.call(resource)
|
||||
resource.normalize!(self)
|
||||
|
||||
resource.formats.each do |format|
|
||||
resource.actions.each do |action|
|
||||
if self.respond_to? :"make_#{action}_#{format}_tests"
|
||||
self.send(:"make_#{action}_#{format}_tests", resource)
|
||||
else
|
||||
should "test #{action} #{format}" do
|
||||
flunk "Test for #{action} as #{format} not implemented"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# :section: Test macros
|
||||
|
||||
# Macro that creates a test asserting that the flash contains the given value.
|
||||
# val can be a String, a Regex, or nil (indicating that the flash should not be set)
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_set_the_flash_to "Thank you for placing this order."
|
||||
# should_set_the_flash_to /created/i
|
||||
# should_set_the_flash_to nil
|
||||
def should_set_the_flash_to(val)
|
||||
if val
|
||||
should "have #{val.inspect} in the flash" do
|
||||
assert_contains flash.values, val, ", Flash: #{flash.inspect}"
|
||||
end
|
||||
else
|
||||
should "not set the flash" do
|
||||
assert_equal({}, flash, "Flash was set to:\n#{flash.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the flash is empty. Same as
|
||||
# @should_set_the_flash_to nil@
|
||||
def should_not_set_the_flash
|
||||
should_set_the_flash_to nil
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller assigned to @name
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_assign_to :user
|
||||
def should_assign_to(name)
|
||||
should "assign @#{name}" do
|
||||
assert assigns(name.to_sym), "The action isn't assigning to @#{name}"
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller did not assign to @name
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# should_not_assign_to :user
|
||||
def should_not_assign_to(name)
|
||||
should "not assign to @#{name}" do
|
||||
assert !assigns(name.to_sym), "@#{name} was visible"
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller responded with a 'response' status code.
|
||||
# Example:
|
||||
#
|
||||
# should_respond_with :success
|
||||
def should_respond_with(response)
|
||||
should "respond with #{response}" do
|
||||
assert_response response
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller rendered the given template.
|
||||
# Example:
|
||||
#
|
||||
# should_render_template :new
|
||||
def should_render_template(template)
|
||||
should "render '#{template}' template" do
|
||||
assert_template template.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the controller returned a redirect to the given path.
|
||||
# The given string is evaled to produce the resulting redirect path. All of the instance variables
|
||||
# set by the controller are available to the evaled string.
|
||||
# Example:
|
||||
#
|
||||
# should_redirect_to '"/"'
|
||||
# should_redirect_to "users_url(@user)"
|
||||
def should_redirect_to(url)
|
||||
should "redirect to \"#{url}\"" do
|
||||
instantiate_variables_from_assigns do
|
||||
assert_redirected_to eval(url, self.send(:binding), __FILE__, __LINE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Macro that creates a test asserting that the rendered view contains a <form> element.
|
||||
def should_render_a_form
|
||||
should "display a form" do
|
||||
assert_select "form", true, "The template doesn't contain a <form> element"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods # :nodoc:
|
||||
|
||||
private # :enddoc:
|
||||
|
||||
SPECIAL_INSTANCE_VARIABLES = %w{
|
||||
_cookies
|
||||
_flash
|
||||
_headers
|
||||
_params
|
||||
_request
|
||||
_response
|
||||
_session
|
||||
action_name
|
||||
before_filter_chain_aborted
|
||||
cookies
|
||||
flash
|
||||
headers
|
||||
ignore_missing_templates
|
||||
logger
|
||||
params
|
||||
request
|
||||
request_origin
|
||||
response
|
||||
session
|
||||
template
|
||||
template_class
|
||||
template_root
|
||||
url
|
||||
variables_added
|
||||
}.map(&:to_s)
|
||||
|
||||
def instantiate_variables_from_assigns(*names, &blk)
|
||||
old = {}
|
||||
names = (@response.template.assigns.keys - SPECIAL_INSTANCE_VARIABLES) if names.empty?
|
||||
names.each do |name|
|
||||
old[name] = instance_variable_get("@#{name}")
|
||||
instance_variable_set("@#{name}", assigns(name.to_sym))
|
||||
end
|
||||
blk.call
|
||||
names.each do |name|
|
||||
instance_variable_set("@#{name}", old[name])
|
||||
end
|
||||
end
|
||||
|
||||
def get_existing_record(res) # :nodoc:
|
||||
returning(instance_variable_get("@#{res.object}")) do |record|
|
||||
assert(record, "This test requires you to set @#{res.object} in your setup block")
|
||||
end
|
||||
end
|
||||
|
||||
def make_parent_params(resource, record = nil, parent_names = nil) # :nodoc:
|
||||
parent_names ||= resource.parents.reverse
|
||||
return {} if parent_names == [] # Base case
|
||||
parent_name = parent_names.shift
|
||||
parent = record ? record.send(parent_name) : parent_name.to_s.classify.constantize.find(:first)
|
||||
|
||||
{ :"#{parent_name}_id" => parent.id }.merge(make_parent_params(resource, parent, parent_names))
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
module Controller # :nodoc:
|
||||
module HTML # :nodoc: all
|
||||
def self.included(other)
|
||||
other.class_eval do
|
||||
extend ThoughtBot::Shoulda::Controller::HTML::ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def make_show_html_tests(res)
|
||||
context "on GET to :show" do
|
||||
setup do
|
||||
record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, record)
|
||||
get :show, parent_params.merge({ res.identifier => record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:show)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_render_template :show
|
||||
should_not_set_the_flash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_edit_html_tests(res)
|
||||
context "on GET to :edit" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
get :edit, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:edit)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_render_template :edit
|
||||
should_not_set_the_flash
|
||||
should_render_a_form
|
||||
should "set @#{res.object} to requested instance" do
|
||||
assert_equal @record, assigns(res.object)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_index_html_tests(res)
|
||||
context "on GET to :index" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
get(:index, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:index)
|
||||
should_not_assign_to res.object.to_s.pluralize
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_respond_with :success
|
||||
should_assign_to res.object.to_s.pluralize
|
||||
should_render_template :index
|
||||
should_not_set_the_flash
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_new_html_tests(res)
|
||||
context "on GET to :new" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
get(:new, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:new)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_respond_with :success
|
||||
should_assign_to res.object
|
||||
should_not_set_the_flash
|
||||
should_render_template :new
|
||||
should_render_a_form
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_destroy_html_tests(res)
|
||||
context "on DELETE to :destroy" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
delete :destroy, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:destroy)
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
|
||||
should "not destroy record" do
|
||||
assert_nothing_raised { assert @record.reload }
|
||||
end
|
||||
else
|
||||
should_set_the_flash_to res.destroy.flash
|
||||
if res.destroy.redirect.is_a? Symbol
|
||||
should_respond_with res.destroy.redirect
|
||||
else
|
||||
should_redirect_to res.destroy.redirect
|
||||
end
|
||||
|
||||
should "destroy record" do
|
||||
assert_raises(::ActiveRecord::RecordNotFound) { @record.reload }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_create_html_tests(res)
|
||||
context "on POST to :create with #{res.create.params.inspect}" do
|
||||
setup do
|
||||
record = get_existing_record(res) rescue nil
|
||||
parent_params = make_parent_params(res, record)
|
||||
@count = res.klass.count
|
||||
post :create, parent_params.merge(res.object => res.create.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:create)
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
should_not_assign_to res.object
|
||||
|
||||
should "not create new record" do
|
||||
assert_equal @count, res.klass.count
|
||||
end
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_set_the_flash_to res.create.flash
|
||||
if res.create.redirect.is_a? Symbol
|
||||
should_respond_with res.create.redirect
|
||||
else
|
||||
should_redirect_to res.create.redirect
|
||||
end
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_update_html_tests(res)
|
||||
context "on PUT to :update with #{res.create.params.inspect}" do
|
||||
setup do
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:update)
|
||||
should_not_assign_to res.object
|
||||
should_redirect_to res.denied.redirect
|
||||
should_set_the_flash_to res.denied.flash
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_set_the_flash_to(res.update.flash)
|
||||
if res.update.redirect.is_a? Symbol
|
||||
should_respond_with res.update.redirect
|
||||
else
|
||||
should_redirect_to res.update.redirect
|
||||
end
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,162 +0,0 @@
|
||||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
module Controller # :nodoc:
|
||||
module XML
|
||||
def self.included(other) #:nodoc:
|
||||
other.class_eval do
|
||||
extend ThoughtBot::Shoulda::Controller::XML::ClassMethods
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Macro that creates a test asserting that the controller responded with an XML content-type
|
||||
# and that the XML contains +<name/>+ as the root element.
|
||||
def should_respond_with_xml_for(name = nil)
|
||||
should "have ContentType set to 'application/xml'" do
|
||||
assert_xml_response
|
||||
end
|
||||
|
||||
if name
|
||||
should "return <#{name}/> as the root element" do
|
||||
body = @response.body.first(100).map {|l| " #{l}"}
|
||||
assert_select name.to_s.dasherize, 1, "Body:\n#{body}...\nDoes not have <#{name}/> as the root element."
|
||||
end
|
||||
end
|
||||
end
|
||||
alias should_respond_with_xml should_respond_with_xml_for
|
||||
|
||||
protected
|
||||
|
||||
def make_show_xml_tests(res) # :nodoc:
|
||||
context "on GET to :show as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, record)
|
||||
get :show, parent_params.merge({ res.identifier => record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:show)
|
||||
should_not_assign_to res.object
|
||||
should_respond_with 401
|
||||
else
|
||||
should_assign_to res.object
|
||||
should_respond_with :success
|
||||
should_respond_with_xml_for res.object
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_edit_xml_tests(res) # :nodoc:
|
||||
# XML doesn't need an :edit action
|
||||
end
|
||||
|
||||
def make_new_xml_tests(res) # :nodoc:
|
||||
# XML doesn't need a :new action
|
||||
end
|
||||
|
||||
def make_index_xml_tests(res) # :nodoc:
|
||||
context "on GET to :index as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
parent_params = make_parent_params(res)
|
||||
get(:index, parent_params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:index)
|
||||
should_not_assign_to res.object.to_s.pluralize
|
||||
should_respond_with 401
|
||||
else
|
||||
should_respond_with :success
|
||||
should_respond_with_xml_for res.object.to_s.pluralize
|
||||
should_assign_to res.object.to_s.pluralize
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_destroy_xml_tests(res) # :nodoc:
|
||||
context "on DELETE to :destroy as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
delete :destroy, parent_params.merge({ res.identifier => @record.to_param })
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:destroy)
|
||||
should_respond_with 401
|
||||
|
||||
should "not destroy record" do
|
||||
assert @record.reload
|
||||
end
|
||||
else
|
||||
should "destroy record" do
|
||||
assert_raises(::ActiveRecord::RecordNotFound) { @record.reload }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_create_xml_tests(res) # :nodoc:
|
||||
context "on POST to :create as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
parent_params = make_parent_params(res)
|
||||
@count = res.klass.count
|
||||
post :create, parent_params.merge(res.object => res.create.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:create)
|
||||
should_respond_with 401
|
||||
should_not_assign_to res.object
|
||||
|
||||
should "not create new record" do
|
||||
assert_equal @count, res.klass.count
|
||||
end
|
||||
else
|
||||
should_assign_to res.object
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def make_update_xml_tests(res) # :nodoc:
|
||||
context "on PUT to :update as xml" do
|
||||
setup do
|
||||
request_xml
|
||||
@record = get_existing_record(res)
|
||||
parent_params = make_parent_params(res, @record)
|
||||
put :update, parent_params.merge(res.identifier => @record.to_param, res.object => res.update.params)
|
||||
end
|
||||
|
||||
if res.denied.actions.include?(:update)
|
||||
should_not_assign_to res.object
|
||||
should_respond_with 401
|
||||
else
|
||||
should_assign_to res.object
|
||||
|
||||
should "not have errors on @#{res.object}" do
|
||||
assert_equal [], assigns(res.object).errors.full_messages, "@#{res.object} has errors:"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Sets the next request's format to 'application/xml'
|
||||
def request_xml
|
||||
@request.accept = "application/xml"
|
||||
end
|
||||
|
||||
# Asserts that the controller's response was 'application/xml'
|
||||
def assert_xml_response
|
||||
assert_equal "application/xml", @response.content_type, "Body: #{@response.body.first(100).chomp}..."
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,14 +0,0 @@
|
||||
# Stolen straight from ActiveSupport
|
||||
|
||||
class Proc #:nodoc:
|
||||
def bind(object)
|
||||
block, time = self, Time.now
|
||||
(class << object; self end).class_eval do
|
||||
method_name = "__bind_#{time.to_i}_#{time.usec}"
|
||||
define_method(method_name, &block)
|
||||
method = instance_method(method_name)
|
||||
remove_method(method_name)
|
||||
method
|
||||
end.bind(object)
|
||||
end
|
||||
end
|
||||
165
vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb
vendored
165
vendor/plugins/shoulda/lib/shoulda/gem/shoulda.rb
vendored
@@ -1,165 +0,0 @@
|
||||
require File.join(File.dirname(__FILE__), 'proc_extensions')
|
||||
|
||||
module Thoughtbot
|
||||
class Shoulda
|
||||
VERSION = '1.0.0'
|
||||
|
||||
# = context and should blocks
|
||||
#
|
||||
# A context block groups should statements under a common setup/teardown method.
|
||||
# Context blocks can be arbitrarily nested, and can do wonders for improving the maintainability
|
||||
# and readability of your test code.
|
||||
#
|
||||
# A context block can contain setup, should, should_eventually, and teardown blocks.
|
||||
#
|
||||
# class UserTest << Test::Unit::TestCase
|
||||
# context "a User instance" do
|
||||
# setup do
|
||||
# @user = User.find(:first)
|
||||
# end
|
||||
#
|
||||
# should "return its full name"
|
||||
# assert_equal 'John Doe', @user.full_name
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This code will produce the method <tt>"test a User instance should return its full name"</tt>.
|
||||
#
|
||||
# Contexts may be nested. Nested contexts run their setup blocks from out to in before each test.
|
||||
# They then run their teardown blocks from in to out after each test.
|
||||
#
|
||||
# class UserTest << Test::Unit::TestCase
|
||||
# context "a User instance" do
|
||||
# setup do
|
||||
# @user = User.find(:first)
|
||||
# end
|
||||
#
|
||||
# should "return its full name"
|
||||
# assert_equal 'John Doe', @user.full_name
|
||||
# end
|
||||
#
|
||||
# context "with a profile" do
|
||||
# setup do
|
||||
# @user.profile = Profile.find(:first)
|
||||
# end
|
||||
#
|
||||
# should "return true when sent :has_profile?"
|
||||
# assert @user.has_profile?
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This code will produce the following methods
|
||||
# * <tt>"test: a User instance should return its full name."</tt>
|
||||
# * <tt>"test: a User instance with a profile should return true when sent :has_profile?."</tt>
|
||||
#
|
||||
# <b>A context block can exist next to normal <tt>def test_the_old_way; end</tt> tests</b>,
|
||||
# meaning you do not have to fully commit to the context/should syntax in a test file.
|
||||
#
|
||||
|
||||
module ClassMethods
|
||||
def self.included(other) # :nodoc:
|
||||
@@context_names = []
|
||||
@@setup_blocks = []
|
||||
@@teardown_blocks = []
|
||||
end
|
||||
|
||||
# Defines a test method. Can be called either inside our outside of a context.
|
||||
# Optionally specify <tt>:unimplimented => true</tt> (see should_eventually).
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class UserTest << Test::Unit::TestCase
|
||||
# should "return first user on find(:first)"
|
||||
# assert_equal users(:first), User.find(:first)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Would create a test named
|
||||
# 'test: should return first user on find(:first)'
|
||||
#
|
||||
def should(name, opts = {}, &should_block)
|
||||
test_name = ["test:", @@context_names, "should", "#{name}. "].flatten.join(' ').to_sym
|
||||
|
||||
name_defined = eval("self.instance_methods.include?('#{test_name.to_s.gsub(/['"]/, '\$1')}')", should_block.binding)
|
||||
raise ArgumentError, "'#{test_name}' is already defined" and return if name_defined
|
||||
|
||||
setup_blocks = @@setup_blocks.dup
|
||||
teardown_blocks = @@teardown_blocks.dup
|
||||
|
||||
if opts[:unimplemented]
|
||||
define_method test_name do |*args|
|
||||
# XXX find a better way of doing this.
|
||||
assert true
|
||||
STDOUT.putc "X" # Tests for this model are missing.
|
||||
end
|
||||
else
|
||||
define_method test_name do |*args|
|
||||
begin
|
||||
setup_blocks.each {|b| b.bind(self).call }
|
||||
should_block.bind(self).call(*args)
|
||||
ensure
|
||||
teardown_blocks.reverse.each {|b| b.bind(self).call }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a context block with the given name.
|
||||
def context(name, &context_block)
|
||||
saved_setups = @@setup_blocks.dup
|
||||
saved_teardowns = @@teardown_blocks.dup
|
||||
saved_contexts = @@context_names.dup
|
||||
|
||||
@@setup_defined = false
|
||||
|
||||
@@context_names << name
|
||||
context_block.bind(self).call
|
||||
|
||||
@@context_names = saved_contexts
|
||||
@@setup_blocks = saved_setups
|
||||
@@teardown_blocks = saved_teardowns
|
||||
end
|
||||
|
||||
# Run before every should block in the current context.
|
||||
# If a setup block appears in a nested context, it will be run after the setup blocks
|
||||
# in the parent contexts.
|
||||
def setup(&setup_block)
|
||||
if @@setup_defined
|
||||
raise RuntimeError, "Either you have two setup blocks in one context, " +
|
||||
"or a setup block outside of a context. Both are equally bad."
|
||||
end
|
||||
@@setup_defined = true
|
||||
|
||||
@@setup_blocks << setup_block
|
||||
end
|
||||
|
||||
# Run after every should block in the current context.
|
||||
# If a teardown block appears in a nested context, it will be run before the teardown
|
||||
# blocks in the parent contexts.
|
||||
def teardown(&teardown_block)
|
||||
@@teardown_blocks << teardown_block
|
||||
end
|
||||
|
||||
# Defines a specification that is not yet implemented.
|
||||
# Will be displayed as an 'X' when running tests, and failures will not be shown.
|
||||
# This is equivalent to:
|
||||
# should(name, {:unimplemented => true}, &block)
|
||||
def should_eventually(name, &block)
|
||||
should("eventually #{name}", {:unimplemented => true}, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Test # :nodoc: all
|
||||
module Unit
|
||||
class TestCase
|
||||
class << self
|
||||
include Thoughtbot::Shoulda::ClassMethods
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
101
vendor/plugins/shoulda/lib/shoulda/general.rb
vendored
101
vendor/plugins/shoulda/lib/shoulda/general.rb
vendored
@@ -1,101 +0,0 @@
|
||||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
module General
|
||||
def self.included(other) # :nodoc:
|
||||
other.class_eval do
|
||||
extend ThoughtBot::Shoulda::General::ClassMethods
|
||||
# include ThoughtBot::Shoulda::General::InstanceMethods
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Loads all fixture files (<tt>test/fixtures/*.yml</tt>)
|
||||
def load_all_fixtures
|
||||
all_fixtures = Dir.glob(File.join(Test::Unit::TestCase.fixture_path, "*.yml")).collect do |f|
|
||||
File.basename(f, '.yml').to_sym
|
||||
end
|
||||
fixtures *all_fixtures
|
||||
end
|
||||
end
|
||||
|
||||
# Prints a message to stdout, tagged with the name of the calling method.
|
||||
def report!(msg = "")
|
||||
puts("#{caller.first}: #{msg}")
|
||||
end
|
||||
|
||||
# Asserts that two arrays contain the same elements, the same number of times. Essentially ==, but unordered.
|
||||
#
|
||||
# assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes
|
||||
def assert_same_elements(a1, a2, msg = nil)
|
||||
[:select, :inject, :size].each do |m|
|
||||
[a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") }
|
||||
end
|
||||
|
||||
assert a1h = a1.inject({}) { |h,e| h[e] = a1.select { |i| i == e }.size; h }
|
||||
assert a2h = a2.inject({}) { |h,e| h[e] = a2.select { |i| i == e }.size; h }
|
||||
|
||||
assert_equal(a1h, a2h, msg)
|
||||
end
|
||||
|
||||
# Asserts that the given collection contains item x. If x is a regular expression, ensure that
|
||||
# at least one element from the collection matches x. +extra_msg+ is appended to the error message if the assertion fails.
|
||||
#
|
||||
# assert_contains(['a', '1'], /\d/) => passes
|
||||
# assert_contains(['a', '1'], 'a') => passes
|
||||
# assert_contains(['a', '1'], /not there/) => fails
|
||||
def assert_contains(collection, x, extra_msg = "")
|
||||
collection = [collection] unless collection.is_a?(Array)
|
||||
msg = "#{x.inspect} not found in #{collection.to_a.inspect} " + extra_msg
|
||||
case x
|
||||
when Regexp: assert(collection.detect { |e| e =~ x }, msg)
|
||||
else assert(collection.include?(x), msg)
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the given collection does not contain item x. If x is a regular expression, ensure that
|
||||
# none of the elements from the collection match x.
|
||||
def assert_does_not_contain(collection, x, extra_msg = "")
|
||||
collection = [collection] unless collection.is_a?(Array)
|
||||
msg = "#{x.inspect} found in #{collection.to_a.inspect} " + extra_msg
|
||||
case x
|
||||
when Regexp: assert(!collection.detect { |e| e =~ x }, msg)
|
||||
else assert(!collection.include?(x), msg)
|
||||
end
|
||||
end
|
||||
|
||||
# Asserts that the given object can be saved
|
||||
#
|
||||
# assert_save User.new(params)
|
||||
def assert_save(obj)
|
||||
assert obj.save, "Errors: #{obj.errors.full_messages.join('; ')}"
|
||||
obj.reload
|
||||
end
|
||||
|
||||
# Asserts that the given object is valid
|
||||
#
|
||||
# assert_save User.new(params)
|
||||
def assert_valid(obj)
|
||||
assert obj.valid?, "Errors: #{obj.errors.full_messages.join('; ')}"
|
||||
end
|
||||
|
||||
# Asserts that the block uses ActionMailer to send emails
|
||||
#
|
||||
# assert_sends_email(2) { Mailer.deliver_messages }
|
||||
def assert_sends_email(num = 1, &blk)
|
||||
ActionMailer::Base.deliveries.clear
|
||||
blk.call
|
||||
msg = "Sent #{ActionMailer::Base.deliveries.size} emails, when #{num} expected:\n"
|
||||
ActionMailer::Base.deliveries.each { |m| msg << " '#{m.subject}' sent to #{m.to.to_sentence}\n" }
|
||||
assert(num == ActionMailer::Base.deliveries.size, msg)
|
||||
end
|
||||
|
||||
# Asserts that the block does not send emails thorough ActionMailer
|
||||
#
|
||||
# assert_does_not_send_email { # do nothing }
|
||||
def assert_does_not_send_email(&blk)
|
||||
assert_sends_email 0, &blk
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,17 +0,0 @@
|
||||
module ThoughtBot # :nodoc:
|
||||
module Shoulda # :nodoc:
|
||||
module Private # :nodoc:
|
||||
def get_options!(args, *wanted)
|
||||
ret = []
|
||||
opts = (args.last.is_a?(Hash) ? args.pop : {})
|
||||
wanted.each {|w| ret << opts.delete(w)}
|
||||
raise ArgumentError, "Unsuported options given: #{opts.keys.join(', ')}" unless opts.keys.empty?
|
||||
return *ret
|
||||
end
|
||||
|
||||
def model_class
|
||||
self.name.gsub(/Test$/, '').constantize
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
40
vendor/plugins/shoulda/tasks/list_tests.rake
vendored
40
vendor/plugins/shoulda/tasks/list_tests.rake
vendored
@@ -1,40 +0,0 @@
|
||||
namespace :shoulda do
|
||||
desc "List the names of the test methods in a specification like format"
|
||||
task :list do
|
||||
|
||||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
require 'active_support'
|
||||
|
||||
# bug in test unit. Set to true to stop from running.
|
||||
Test::Unit.run = true
|
||||
|
||||
test_files = Dir.glob(File.join('test', '**', '*_test.rb'))
|
||||
test_files.each do |file|
|
||||
load file
|
||||
klass = File.basename(file, '.rb').classify.constantize
|
||||
|
||||
puts
|
||||
puts "#{klass.name.gsub(/Test$/, '')}"
|
||||
test_methods = klass.instance_methods.grep(/^test/).map {|s| s.gsub(/^test: /, '')}.sort
|
||||
test_methods.each {|m| puts " - #{m}" }
|
||||
# puts "#{klass.name.gsub(/Test$/, '')}"
|
||||
# test_methods = klass.instance_methods.grep(/^test/).sort
|
||||
#
|
||||
# method_hash = test_methods.inject({}) do |h, name|
|
||||
# header = name.gsub(/^test: (.*)should.*$/, '\1')
|
||||
# test = name.gsub(/^test:.*should (.*)$/, '\1')
|
||||
# h[header] ||= []
|
||||
# h[header] << test
|
||||
# h
|
||||
# end
|
||||
#
|
||||
# method_hash.keys.sort.each do |header|
|
||||
# puts " #{header.chomp} should"
|
||||
# method_hash[header].each do |test|
|
||||
# puts " - #{test}"
|
||||
# end
|
||||
# end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace :shoulda do
|
||||
# From http://blog.internautdesign.com/2007/11/2/a-yaml_to_shoulda-rake-task
|
||||
# David.Lowenfels@gmail.com
|
||||
desc "Converts a YAML file (FILE=./path/to/yaml) into a Shoulda skeleton"
|
||||
task :from_yaml do
|
||||
require 'yaml'
|
||||
|
||||
def yaml_to_context(hash, indent = 0)
|
||||
indent1 = ' ' * indent
|
||||
indent2 = ' ' * (indent + 1)
|
||||
hash.each_pair do |context, shoulds|
|
||||
puts indent1 + "context \"#{context}\" do"
|
||||
puts
|
||||
shoulds.each do |should|
|
||||
yaml_to_context( should, indent + 1 ) and next if should.is_a?( Hash )
|
||||
puts indent2 + "should_eventually \"" + should.gsub(/^should +/,'') + "\" do"
|
||||
puts indent2 + "end"
|
||||
puts
|
||||
end
|
||||
puts indent1 + "end"
|
||||
end
|
||||
end
|
||||
|
||||
puts("Please pass in a FILE argument.") and exit unless ENV['FILE']
|
||||
|
||||
yaml_to_context( YAML.load_file( ENV['FILE'] ) )
|
||||
end
|
||||
end
|
||||
8
vendor/plugins/shoulda/test/README
vendored
8
vendor/plugins/shoulda/test/README
vendored
@@ -1,8 +0,0 @@
|
||||
The tests for should have two dependencies that I know of:
|
||||
|
||||
* Rails version 1.2.3
|
||||
* A working sqlite3 installation.
|
||||
|
||||
If you have problems running these tests, please notify the shoulda mailing list: shoulda@googlegroups.com
|
||||
|
||||
- Tammer Saleh
|
||||
@@ -1,5 +0,0 @@
|
||||
first:
|
||||
id: 1
|
||||
title: My Cute Kitten!
|
||||
body: This is totally a cute kitten
|
||||
user_id: 1
|
||||
@@ -1,9 +0,0 @@
|
||||
first:
|
||||
id: 1
|
||||
name: Stuff
|
||||
second:
|
||||
id: 2
|
||||
name: Rails
|
||||
third:
|
||||
id: 3
|
||||
name: Nothing
|
||||
@@ -1,5 +0,0 @@
|
||||
first:
|
||||
id: 1
|
||||
name: Some dude
|
||||
age: 2
|
||||
email: none@none.com
|
||||
@@ -1,43 +0,0 @@
|
||||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
require 'posts_controller'
|
||||
|
||||
# Re-raise errors caught by the controller.
|
||||
class PostsController; def rescue_action(e) raise e end; end
|
||||
|
||||
class PostsControllerTest < Test::Unit::TestCase
|
||||
load_all_fixtures
|
||||
|
||||
def setup
|
||||
@controller = PostsController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
@post = Post.find(:first)
|
||||
end
|
||||
|
||||
context "The public" do
|
||||
setup do
|
||||
@request.session[:logged_in] = false
|
||||
end
|
||||
|
||||
should_be_restful do |resource|
|
||||
resource.parent = :user
|
||||
|
||||
resource.denied.actions = [:index, :show, :edit, :new, :create, :update, :destroy]
|
||||
resource.denied.flash = /what/i
|
||||
resource.denied.redirect = '"/"'
|
||||
end
|
||||
end
|
||||
|
||||
context "Logged in" do
|
||||
setup do
|
||||
@request.session[:logged_in] = true
|
||||
end
|
||||
|
||||
should_be_restful do |resource|
|
||||
resource.parent = :user
|
||||
|
||||
resource.create.params = { :title => "first post", :body => 'blah blah blah'}
|
||||
resource.update.params = { :title => "changed" }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,36 +0,0 @@
|
||||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
require 'users_controller'
|
||||
|
||||
# Re-raise errors caught by the controller.
|
||||
class UsersController; def rescue_action(e) raise e end; end
|
||||
|
||||
class UsersControllerTest < Test::Unit::TestCase
|
||||
load_all_fixtures
|
||||
|
||||
def setup
|
||||
@controller = UsersController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
@user = User.find(:first)
|
||||
end
|
||||
|
||||
should_be_restful do |resource|
|
||||
resource.identifier = :id
|
||||
resource.klass = User
|
||||
resource.object = :user
|
||||
resource.parent = []
|
||||
resource.actions = [:index, :show, :new, :edit, :update, :create, :destroy]
|
||||
resource.formats = [:html, :xml]
|
||||
|
||||
resource.create.params = { :name => "bob", :email => 'bob@bob.com', :age => 13}
|
||||
resource.update.params = { :name => "sue" }
|
||||
|
||||
resource.create.redirect = "user_url(@user)"
|
||||
resource.update.redirect = "user_url(@user)"
|
||||
resource.destroy.redirect = "users_url"
|
||||
|
||||
resource.create.flash = /created/i
|
||||
resource.update.flash = /updated/i
|
||||
resource.destroy.flash = /removed/i
|
||||
end
|
||||
end
|
||||
@@ -1,71 +0,0 @@
|
||||
require File.join(File.dirname(__FILE__), '..', 'test_helper')
|
||||
|
||||
class ContextTest < Test::Unit::TestCase # :nodoc:
|
||||
|
||||
context "context with setup block" do
|
||||
setup do
|
||||
@blah = "blah"
|
||||
end
|
||||
|
||||
should "have @blah == 'blah'" do
|
||||
assert_equal "blah", @blah
|
||||
end
|
||||
|
||||
should "have name set right" do
|
||||
assert_match(/^test: context with setup block/, self.to_s)
|
||||
end
|
||||
|
||||
context "and a subcontext" do
|
||||
setup do
|
||||
@blah = "#{@blah} twice"
|
||||
end
|
||||
|
||||
should "be named correctly" do
|
||||
assert_match(/^test: context with setup block and a subcontext should be named correctly/, self.to_s)
|
||||
end
|
||||
|
||||
should "run the setup methods in order" do
|
||||
assert_equal @blah, "blah twice"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "another context with setup block" do
|
||||
setup do
|
||||
@blah = "foo"
|
||||
end
|
||||
|
||||
should "have @blah == 'foo'" do
|
||||
assert_equal "foo", @blah
|
||||
end
|
||||
|
||||
should "have name set right" do
|
||||
assert_match(/^test: another context with setup block/, self.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
context "context with method definition" do
|
||||
setup do
|
||||
def hello; "hi"; end
|
||||
end
|
||||
|
||||
should "be able to read that method" do
|
||||
assert_equal "hi", hello
|
||||
end
|
||||
|
||||
should "have name set right" do
|
||||
assert_match(/^test: context with method definition/, self.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
context "another context" do
|
||||
should "not define @blah" do
|
||||
assert_nil @blah
|
||||
end
|
||||
end
|
||||
|
||||
should_eventually "should pass, since it's unimplemented" do
|
||||
flunk "what?"
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,40 +0,0 @@
|
||||
require File.join(File.dirname(__FILE__), '..', 'test_helper')
|
||||
|
||||
class Val
|
||||
@@val = 0
|
||||
def self.val; @@val; end
|
||||
def self.inc(i=1); @@val += i; end
|
||||
end
|
||||
|
||||
class HelpersTest < Test::Unit::TestCase # :nodoc:
|
||||
|
||||
context "an array of values" do
|
||||
setup do
|
||||
@a = ['abc', 'def', 3]
|
||||
end
|
||||
|
||||
[/b/, 'abc', 3].each do |x|
|
||||
should "contain #{x.inspect}" do
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_does_not_contain @a, x
|
||||
end
|
||||
assert_contains @a, x
|
||||
end
|
||||
end
|
||||
|
||||
should "not contain 'wtf'" do
|
||||
assert_raises(Test::Unit::AssertionFailedError) {assert_contains @a, 'wtf'}
|
||||
assert_does_not_contain @a, 'wtf'
|
||||
end
|
||||
|
||||
should "be the same as another array, ordered differently" do
|
||||
assert_same_elements(@a, [3, "def", "abc"])
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_same_elements(@a, [3, 3, "def", "abc"])
|
||||
end
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_same_elements([@a, "abc"].flatten, [3, 3, "def", "abc"])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,26 +0,0 @@
|
||||
require File.join(File.dirname(__FILE__), '..', 'test_helper')
|
||||
|
||||
class PrivateHelpersTest < Test::Unit::TestCase # :nodoc:
|
||||
include ThoughtBot::Shoulda::ActiveRecord
|
||||
context "get_options!" do
|
||||
should "remove opts from args" do
|
||||
args = [:a, :b, {}]
|
||||
get_options!(args)
|
||||
assert_equal [:a, :b], args
|
||||
end
|
||||
|
||||
should "return wanted opts in order" do
|
||||
args = [{:one => 1, :two => 2}]
|
||||
one, two = get_options!(args, :one, :two)
|
||||
assert_equal 1, one
|
||||
assert_equal 2, two
|
||||
end
|
||||
|
||||
should "raise ArgumentError if given unwanted option" do
|
||||
args = [{:one => 1, :two => 2}]
|
||||
assert_raises ArgumentError do
|
||||
get_options!(args, :one)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
10
vendor/plugins/shoulda/test/rails_root/Rakefile
vendored
10
vendor/plugins/shoulda/test/rails_root/Rakefile
vendored
@@ -1,10 +0,0 @@
|
||||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
|
||||
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
require 'tasks/rails'
|
||||
@@ -1,25 +0,0 @@
|
||||
# Filters added to this controller apply to all controllers in the application.
|
||||
# Likewise, all the methods added will be available for all controllers.
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
# Pick a unique cookie name to distinguish our session data from others'
|
||||
session :session_key => '_rails_root_session_id'
|
||||
|
||||
def ensure_logged_in
|
||||
unless session[:logged_in]
|
||||
respond_to do |accepts|
|
||||
accepts.html do
|
||||
flash[:error] = 'What do you think you\'re doing?'
|
||||
redirect_to '/'
|
||||
end
|
||||
accepts.xml do
|
||||
headers["Status"] = "Unauthorized"
|
||||
headers["WWW-Authenticate"] = %(Basic realm="Web Password")
|
||||
render :text => "Couldn't authenticate you", :status => '401 Unauthorized'
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -1,78 +0,0 @@
|
||||
class PostsController < ApplicationController
|
||||
before_filter :ensure_logged_in
|
||||
before_filter :load_user
|
||||
|
||||
def index
|
||||
@posts = @user.posts
|
||||
|
||||
respond_to do |format|
|
||||
format.html # index.rhtml
|
||||
format.xml { render :xml => @posts.to_xml }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@post = @user.posts.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
format.html # show.rhtml
|
||||
format.xml { render :xml => @post.to_xml }
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
@post = @user.posts.build
|
||||
end
|
||||
|
||||
def edit
|
||||
@post = @user.posts.find(params[:id])
|
||||
end
|
||||
|
||||
def create
|
||||
@post = @user.posts.build(params[:post])
|
||||
|
||||
respond_to do |format|
|
||||
if @post.save
|
||||
flash[:notice] = 'Post was successfully created.'
|
||||
format.html { redirect_to post_url(@post.user, @post) }
|
||||
format.xml { head :created, :location => post_url(@post.user, @post) }
|
||||
else
|
||||
format.html { render :action => "new" }
|
||||
format.xml { render :xml => @post.errors.to_xml }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@post = @user.posts.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
if @post.update_attributes(params[:post])
|
||||
flash[:notice] = 'Post was successfully updated.'
|
||||
format.html { redirect_to post_url(@post.user, @post) }
|
||||
format.xml { head :ok }
|
||||
else
|
||||
format.html { render :action => "edit" }
|
||||
format.xml { render :xml => @post.errors.to_xml }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@post = @user.posts.find(params[:id])
|
||||
@post.destroy
|
||||
|
||||
flash[:notice] = "Post was removed"
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to posts_url(@post.user) }
|
||||
format.xml { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_user
|
||||
@user = User.find(params[:user_id])
|
||||
end
|
||||
end
|
||||
@@ -1,81 +0,0 @@
|
||||
class UsersController < ApplicationController
|
||||
# GET /users
|
||||
# GET /users.xml
|
||||
def index
|
||||
@users = User.find(:all)
|
||||
|
||||
respond_to do |format|
|
||||
format.html # index.rhtml
|
||||
format.xml { render :xml => @users.to_xml }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /users/1
|
||||
# GET /users/1.xml
|
||||
def show
|
||||
@user = User.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
format.html # show.rhtml
|
||||
format.xml { render :xml => @user.to_xml }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /users/new
|
||||
def new
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
# GET /users/1;edit
|
||||
def edit
|
||||
@user = User.find(params[:id])
|
||||
end
|
||||
|
||||
# POST /users
|
||||
# POST /users.xml
|
||||
def create
|
||||
@user = User.new(params[:user])
|
||||
|
||||
respond_to do |format|
|
||||
if @user.save
|
||||
flash[:notice] = 'User was successfully created.'
|
||||
format.html { redirect_to user_url(@user) }
|
||||
format.xml { head :created, :location => user_url(@user) }
|
||||
else
|
||||
format.html { render :action => "new" }
|
||||
format.xml { render :xml => @user.errors.to_xml }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /users/1
|
||||
# PUT /users/1.xml
|
||||
def update
|
||||
@user = User.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
if @user.update_attributes(params[:user])
|
||||
flash[:notice] = 'User was successfully updated.'
|
||||
format.html { redirect_to user_url(@user) }
|
||||
format.xml { head :ok }
|
||||
else
|
||||
format.html { render :action => "edit" }
|
||||
format.xml { render :xml => @user.errors.to_xml }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /users/1
|
||||
# DELETE /users/1.xml
|
||||
def destroy
|
||||
@user = User.find(params[:id])
|
||||
@user.destroy
|
||||
|
||||
flash[:notice] = "User was removed"
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to users_url }
|
||||
format.xml { head :ok }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
# Methods added to this helper will be available to all templates in the application.
|
||||
module ApplicationHelper
|
||||
end
|
||||
@@ -1,2 +0,0 @@
|
||||
module PostsHelper
|
||||
end
|
||||
@@ -1,2 +0,0 @@
|
||||
module UsersHelper
|
||||
end
|
||||
@@ -1,3 +0,0 @@
|
||||
class Dog < ActiveRecord::Base
|
||||
belongs_to :user, :foreign_key => :owner_id
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
class Post < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :owner, :foreign_key => :user_id, :class_name => 'User'
|
||||
has_many :taggings
|
||||
has_many :tags, :through => :taggings
|
||||
|
||||
validates_uniqueness_of :title
|
||||
validates_presence_of :title
|
||||
validates_presence_of :body, :message => 'Seriously... wtf'
|
||||
validates_numericality_of :user_id
|
||||
end
|
||||
@@ -1,4 +0,0 @@
|
||||
class Tag < ActiveRecord::Base
|
||||
has_many :taggings
|
||||
has_many :posts, :through => :taggings
|
||||
end
|
||||
@@ -1,4 +0,0 @@
|
||||
class Tagging < ActiveRecord::Base
|
||||
belongs_to :post
|
||||
belongs_to :tag
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
class User < ActiveRecord::Base
|
||||
has_many :posts
|
||||
has_many :dogs, :foreign_key => :owner_id
|
||||
|
||||
attr_protected :password
|
||||
validates_format_of :email, :with => /\w*@\w*.com/
|
||||
validates_length_of :email, :in => 1..100
|
||||
validates_inclusion_of :age, :in => 1..100
|
||||
end
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
||||
<title>Posts: <%= controller.action_name %></title>
|
||||
<%= stylesheet_link_tag 'scaffold' %>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p style="color: green"><%= flash[:notice] %></p>
|
||||
|
||||
<%= yield %>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
||||
<title>Users: <%= controller.action_name %></title>
|
||||
<%= stylesheet_link_tag 'scaffold' %>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p style="color: green"><%= flash[:notice] %></p>
|
||||
|
||||
<%= yield %>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user