From fc45345e0d17ed181566d042604fd4d7173e115a Mon Sep 17 00:00:00 2001 From: Benjamin Meichsner Date: Tue, 20 Jan 2009 19:37:15 +0100 Subject: [PATCH] Introduced acts_as_paranoid. Avoid deleting of suppliers and articles. (for consistency of order-results) --- app/controllers/ordering_controller.rb | 139 ++++----- app/models/article.rb | 5 +- app/models/group.rb | 12 +- app/models/supplier.rb | 5 +- app/models/user.rb | 1 - config/environment.rb | 1 + db/migrate/20090119155930_acts_as_paranoid.rb | 11 + db/schema.rb | 32 +- lib/tasks/gettext.rake | 50 --- test/fixtures/articles.yml | 2 +- test/fixtures/suppliers.yml | 3 +- vendor/plugins/acts_as_paranoid/CHANGELOG | 74 +++++ vendor/plugins/acts_as_paranoid/MIT-LICENSE | 20 ++ vendor/plugins/acts_as_paranoid/README | 26 ++ .../acts_as_paranoid/RUNNING_UNIT_TESTS | 41 +++ vendor/plugins/acts_as_paranoid/Rakefile | 180 +++++++++++ vendor/plugins/acts_as_paranoid/init.rb | 34 +++ .../belongs_to_with_deleted_association.rb | 14 + ...any_through_without_deleted_association.rb | 27 ++ .../lib/caboose/acts/paranoid.rb | 196 ++++++++++++ .../lib/caboose/acts/paranoid_find_wrapper.rb | 94 ++++++ .../acts_as_paranoid/test/database.yml | 18 ++ .../test/fixtures/categories.yml | 19 ++ .../test/fixtures/categories_widgets.yml | 12 + .../test/fixtures/taggings.yml | 9 + .../acts_as_paranoid/test/fixtures/tags.yml | 6 + .../test/fixtures/widgets.yml | 8 + .../acts_as_paranoid/test/paranoid_test.rb | 287 ++++++++++++++++++ .../plugins/acts_as_paranoid/test/schema.rb | 30 ++ .../acts_as_paranoid/test/test_helper.rb | 33 ++ 30 files changed, 1238 insertions(+), 151 deletions(-) create mode 100644 db/migrate/20090119155930_acts_as_paranoid.rb delete mode 100644 lib/tasks/gettext.rake create mode 100644 vendor/plugins/acts_as_paranoid/CHANGELOG create mode 100644 vendor/plugins/acts_as_paranoid/MIT-LICENSE create mode 100644 vendor/plugins/acts_as_paranoid/README create mode 100644 vendor/plugins/acts_as_paranoid/RUNNING_UNIT_TESTS create mode 100644 vendor/plugins/acts_as_paranoid/Rakefile create mode 100644 vendor/plugins/acts_as_paranoid/init.rb create mode 100644 vendor/plugins/acts_as_paranoid/lib/caboose/acts/belongs_to_with_deleted_association.rb create mode 100644 vendor/plugins/acts_as_paranoid/lib/caboose/acts/has_many_through_without_deleted_association.rb create mode 100644 vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb create mode 100644 vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid_find_wrapper.rb create mode 100644 vendor/plugins/acts_as_paranoid/test/database.yml create mode 100644 vendor/plugins/acts_as_paranoid/test/fixtures/categories.yml create mode 100644 vendor/plugins/acts_as_paranoid/test/fixtures/categories_widgets.yml create mode 100644 vendor/plugins/acts_as_paranoid/test/fixtures/taggings.yml create mode 100644 vendor/plugins/acts_as_paranoid/test/fixtures/tags.yml create mode 100644 vendor/plugins/acts_as_paranoid/test/fixtures/widgets.yml create mode 100644 vendor/plugins/acts_as_paranoid/test/paranoid_test.rb create mode 100644 vendor/plugins/acts_as_paranoid/test/schema.rb create mode 100644 vendor/plugins/acts_as_paranoid/test/test_helper.rb diff --git a/app/controllers/ordering_controller.rb b/app/controllers/ordering_controller.rb index c980b865..ffb6fba8 100644 --- a/app/controllers/ordering_controller.rb +++ b/app/controllers/ordering_controller.rb @@ -2,17 +2,10 @@ # Management actions that require the "orders" role are handled by the OrdersController. class OrderingController < ApplicationController # Security - before_filter :ensureOrdergroupMember - verify :method => :post, :only => [:saveOrder], :redirect_to => { :action => :index } + before_filter :ensure_ordergroup_member + before_filter :ensure_open_order, :only => [:order, :saveOrder] - # Messages - ERROR_ALREADY_FINISHED = 'Diese Bestellung ist bereits abgeschlossen.' - ERROR_NO_ORDERGROUP = 'Sie gehören keiner Bestellgruppe an.' - ERROR_INSUFFICIENT_FUNDS = 'Der Bestellwert übersteigt das verfügbare Guthaben.' - MSG_ORDER_UPDATED = 'Die Bestellung wurde gespeichert.' - MSG_ORDER_CREATED = 'Die Bestellung wurde angelegt.' - ERROR_UPDATE_FAILED = 'Die Bestellung konnte nicht aktualisiert werden, da ein Fehler auftrat.' - ERROR_UPDATE_CONFLICT = 'In der Zwischenzeit hat jemand anderes auch bestellt, daher konnte die Bestellung nicht aktualisiert werden.' + verify :method => :post, :only => [:saveOrder], :redirect_to => { :action => :index } # Index page. def index @@ -29,72 +22,57 @@ class OrderingController < ApplicationController # Edit a current order. def order - @order = Order.find(params[:id], :include => [:supplier, :order_articles]) - if !@order.current? - flash[:notice] = ERROR_ALREADY_FINISHED - redirect_to :action => 'index' - elsif !(@ordergroup = @current_user.find_ordergroup) - flash[:notice] = ERROR_NO_ORDERGROUP - redirect_to :controller => 'index' - else - @current_orders = Order.find_current - @other_orders = @current_orders.reject{|order| order == @order} - # Load order article data... - @articles_by_category = @order.get_articles - # save results of earlier orders in array - ordered_articles = Array.new - @group_order = @order.group_orders.find(:first, :conditions => "ordergroup_id = #{@ordergroup.id}", :include => :group_order_articles) - if @group_order - # Group has already ordered, so get the results... - for article in @group_order.group_order_articles - result = article.orderResult - ordered_articles[article.order_article_id] = { 'quantity' => article.quantity, - 'tolerance' => article.tolerance, - 'quantity_result' => result[:quantity], - 'tolerance_result' => result[:tolerance]} - end - @version = @group_order.lock_version - @availableFunds = @ordergroup.getAvailableFunds(@group_order) - else - @version = 0 - @availableFunds = @ordergroup.getAvailableFunds + @current_orders = Order.find_current + @other_orders = @current_orders.reject{|order| order == @order} + # Load order article data... + @articles_by_category = @order.get_articles + # save results of earlier orders in array + ordered_articles = Array.new + @group_order = @order.group_orders.find(:first, :conditions => "ordergroup_id = #{@ordergroup.id}", :include => :group_order_articles) + if @group_order + # Group has already ordered, so get the results... + for article in @group_order.group_order_articles + result = article.orderResult + ordered_articles[article.order_article_id] = { 'quantity' => article.quantity, + 'tolerance' => article.tolerance, + 'quantity_result' => result[:quantity], + 'tolerance_result' => result[:tolerance]} end - - # load prices .... - @price = Array.new; @unit = Array.new; - @others_quantity = Array.new; @quantity = Array.new; @quantity_result = Array.new; @used_quantity = Array.new; @unused_quantity = Array.new - @others_tolerance = Array.new; @tolerance = Array.new; @tolerance_result = Array.new; @used_tolerance = Array.new; @unused_tolerance = Array.new - i = 0; - @articles_by_category.each do |category, order_articles| - for order_article in order_articles - # price/unit size - @price[i] = order_article.article.gross_price - @unit[i] = order_article.article.unit_quantity - # quantity - @quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['quantity'] : 0) - @others_quantity[i] = order_article.quantity - @quantity[i] - @used_quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['quantity_result'] : 0) - # tolerance - @tolerance[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['tolerance'] : 0) - @others_tolerance[i] = order_article.tolerance - @tolerance[i] - @used_tolerance[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['tolerance_result'] : 0) - i += 1 - end + @version = @group_order.lock_version + @availableFunds = @ordergroup.getAvailableFunds(@group_order) + else + @version = 0 + @availableFunds = @ordergroup.getAvailableFunds + end + + # load prices .... + @price = Array.new; @unit = Array.new; + @others_quantity = Array.new; @quantity = Array.new; @quantity_result = Array.new; @used_quantity = Array.new; @unused_quantity = Array.new + @others_tolerance = Array.new; @tolerance = Array.new; @tolerance_result = Array.new; @used_tolerance = Array.new; @unused_tolerance = Array.new + i = 0; + @articles_by_category.each do |category, order_articles| + for order_article in order_articles + # price/unit size + @price[i] = order_article.article.gross_price + @unit[i] = order_article.article.unit_quantity + # quantity + @quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['quantity'] : 0) + @others_quantity[i] = order_article.quantity - @quantity[i] + @used_quantity[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['quantity_result'] : 0) + # tolerance + @tolerance[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['tolerance'] : 0) + @others_tolerance[i] = order_article.tolerance - @tolerance[i] + @used_tolerance[i] = (ordered_articles[order_article.id] ? ordered_articles[order_article.id]['tolerance_result'] : 0) + i += 1 end end end # Update changes to a current order. def saveOrder - order = Order.find(params[:id], :include => [:supplier, :order_articles]) - if (!order.current?) - flash[:error] = ERROR_ALREADY_FINISHED - redirect_to :action => 'index' - elsif !(ordergroup = @current_user.find_ordergroup) - flash[:error] = ERROR_NO_ORDERGROUP - redirect_to :controller => 'index' - elsif (params[:total_balance].to_i < 0) - flash[:error] = ERROR_INSUFFICIENT_FUNDS + order = @order # Get the object through before_filter + if (params[:total_balance].to_i < 0) + flash[:error] = 'Der Bestellwert übersteigt das verfügbare Guthaben.' redirect_to :action => 'order' elsif (ordered = params[:ordered]) begin @@ -131,12 +109,12 @@ class OrderingController < ApplicationController order.updateQuantities order.save! end - flash[:notice] = MSG_ORDER_UPDATED + flash[:notice] = 'Die Bestellung wurde gespeichert.' rescue ActiveRecord::StaleObjectError - flash[:error] = ERROR_UPDATE_CONFLICT + flash[:error] = 'In der Zwischenzeit hat jemand anderes auch bestellt, daher konnte die Bestellung nicht aktualisiert werden.' rescue => exception logger.error('Failed to update order: ' + exception.message) - flash[:error] = ERROR_UPDATE_FAILED + flash[:error] = 'Die Bestellung konnte nicht aktualisiert werden, da ein Fehler auftrat.' end redirect_to :action => 'my_order_result', :id => order end @@ -213,8 +191,19 @@ class OrderingController < ApplicationController # Returns true if @current_user is member of an Ordergroup. # Used as a :before_filter by OrderingController. - def ensureOrdergroupMember - !@current_user.find_ordergroup.nil? - end + def ensure_ordergroup_member + unless @current_user.find_ordergroup + flash[:notice] = 'Sie gehören keiner Bestellgruppe an.' + redirect_to :controller => root_path + end + end + + def ensure_open_order + @order = Order.find(params[:id], :include => [:supplier, :order_articles]) + unless @order.current? + flash[:notice] = 'Diese Bestellung ist bereits abgeschlossen.' + redirect_to :action => 'index' + end + end end diff --git a/app/models/article.rb b/app/models/article.rb index dbbfc384..66fb85bd 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 20090115232435 +# Schema version: 20090119155930 # # Table name: articles # @@ -22,9 +22,12 @@ # created_at :datetime # updated_at :datetime # quantity :decimal(6, 2) default(0.0) +# deleted_at :datetime # class Article < ActiveRecord::Base + acts_as_paranoid # Avoid deleting the article for consistency of order-results + belongs_to :supplier belongs_to :article_category diff --git a/app/models/group.rb b/app/models/group.rb index f4b59013..5b889851 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -32,10 +32,6 @@ class Group < ActiveRecord::Base validates_length_of :name, :in => 1..25 validates_uniqueness_of :name - # messages - ERR_LAST_ADMIN_GROUP_UPDATE = "Der letzten Gruppe mit Admin-Rechten darf die Admin-Rolle nicht entzogen werden" - ERR_LAST_ADMIN_GROUP_DELETE = "Die letzte Gruppe mit Admin-Rechten darf nicht gelöscht werden" - # Returns true if the given user if is an member of this group. def member?(user) memberships.find_by_user_id(user.id) @@ -54,7 +50,9 @@ class Group < ActiveRecord::Base # Check before destroy a group, if this is the last group with admin role def before_destroy - raise ERR_LAST_ADMIN_GROUP_DELETE if self.role_admin == true && Group.find_all_by_role_admin(true).size == 1 + if self.role_admin == true && Group.find_all_by_role_admin(true).size == 1 + raise "Die letzte Gruppe mit Admin-Rechten darf nicht gelöscht werden" + end end # get all groups, which are NOT Ordergroups @@ -72,7 +70,9 @@ class Group < ActiveRecord::Base # add validation check on update def validate_on_update # error if this is the last group with admin role and role_admin should set to false - errors.add(:role_admin, ERR_LAST_ADMIN_GROUP_UPDATE) if self.role_admin == false && Group.find_all_by_role_admin(true).size == 1 && self == Group.find(:first, :conditions => "role_admin = 1") + if self.role_admin == false && Group.find_all_by_role_admin(true).size == 1 && self == Group.find(:first, :conditions => "role_admin = 1") + errors.add(:role_admin, "Der letzten Gruppe mit Admin-Rechten darf die Admin-Rolle nicht entzogen werden") + end end end diff --git a/app/models/supplier.rb b/app/models/supplier.rb index a30b9308..08b2493f 100644 --- a/app/models/supplier.rb +++ b/app/models/supplier.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 20090102171850 +# Schema version: 20090119155930 # # Table name: suppliers # @@ -18,9 +18,12 @@ # note :string(255) # shared_supplier_id :integer(4) # min_order_quantity :string(255) +# deleted_at :datetime # class Supplier < ActiveRecord::Base + acts_as_paranoid # Avoid deleting the supplier for consistency of order-results + has_many :articles, :dependent => :destroy has_many :orders has_many :deliveries diff --git a/app/models/user.rb b/app/models/user.rb index ce879be0..588f3cd0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -150,7 +150,6 @@ class User < ActiveRecord::Base # Returns the user's Ordergroup or nil if none found. def find_ordergroup ordergroups.first - #groups.find(:first, :conditions => "type = 'Ordergroup'") end # Find all tasks, for which the current user should be responsible diff --git a/config/environment.rb b/config/environment.rb index 37e19c82..90da918f 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -67,6 +67,7 @@ Rails::Initializer.run do |config| # library for parsing/writing files from/to csv-file config.gem "fastercsv" config.gem "prawn" + config.gem "rubyist-aasm", :version => '2.0.5', :source => "http://gems.github.com" # acts_as_statemachine # The internationalization framework can be changed to have another default locale (standard is :en) or more load paths. # All files from config/locales/*.rb,yml are added automatically. diff --git a/db/migrate/20090119155930_acts_as_paranoid.rb b/db/migrate/20090119155930_acts_as_paranoid.rb new file mode 100644 index 00000000..8053f567 --- /dev/null +++ b/db/migrate/20090119155930_acts_as_paranoid.rb @@ -0,0 +1,11 @@ +class ActsAsParanoid < ActiveRecord::Migration + def self.up + add_column :suppliers, :deleted_at, :datetime + add_column :articles, :deleted_at, :datetime + end + + def self.down + remove_column :suppliers, :deleted_at, :datetime + remove_column :articles, :deleted_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 16f9b97f..17826843 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,7 +9,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20090115232435) do +ActiveRecord::Schema.define(:version => 20090119155930) do create_table "article_categories", :force => true do |t| t.string "name", :default => "", :null => false @@ -37,6 +37,7 @@ ActiveRecord::Schema.define(:version => 20090115232435) do t.datetime "created_at" t.datetime "updated_at" t.decimal "quantity", :precision => 6, :scale => 2, :default => 0.0 + t.datetime "deleted_at" end add_index "articles", ["name", "supplier_id"], :name => "index_articles_on_name_and_supplier_id" @@ -251,20 +252,21 @@ ActiveRecord::Schema.define(:version => 20090115232435) do end create_table "suppliers", :force => true do |t| - t.string "name", :default => "", :null => false - t.string "address", :default => "", :null => false - t.string "phone", :default => "", :null => false - t.string "phone2" - t.string "fax" - t.string "email" - t.string "url" - t.string "contact_person" - t.string "customer_number" - t.string "delivery_days" - t.string "order_howto" - t.string "note" - t.integer "shared_supplier_id" - t.string "min_order_quantity" + t.string "name", :default => "", :null => false + t.string "address", :default => "", :null => false + t.string "phone", :default => "", :null => false + t.string "phone2" + t.string "fax" + t.string "email" + t.string "url" + t.string "contact_person" + t.string "customer_number" + t.string "delivery_days" + t.string "order_howto" + t.string "note" + t.integer "shared_supplier_id" + t.string "min_order_quantity" + t.datetime "deleted_at" end add_index "suppliers", ["name"], :name => "index_suppliers_on_name", :unique => true diff --git a/lib/tasks/gettext.rake b/lib/tasks/gettext.rake deleted file mode 100644 index a56a6923..00000000 --- a/lib/tasks/gettext.rake +++ /dev/null @@ -1,50 +0,0 @@ -# these are the tasks to updates localization-data -require 'gettext/utils' - -# Reopen to make RubyGettext's ERB parser parse .html.erb files -module GetText - module ErbParser - @config = { - :extnames => ['.rhtml', '.erb'] - } - end -end - -begin - require "#{RAILS_ROOT}/vendor/plugins/haml/lib/haml" -rescue LoadError - require 'haml' # From gem -end - -module HamlParser - module_function - - def target?(file) - File.extname(file) == '.haml' - end - - def parse(file, ary = []) - haml = Haml::Engine.new(IO.readlines(file).join) - code = haml.precompiled.split(/$/) - RubyParser.parse_lines(file, code, ary) - end -end -GetText::RGetText.add_parser(HamlParser) - -namespace :gettext do - desc 'Create mo-files for L10n' - task :makemo do - GetText.create_mofiles(true, 'po', 'locale') - end - - desc 'Update pot/po files to match new version' - task :updatepo do - TEXT_DOMAIN = 'foodsoft' - APP_VERSION = '2.0' - files = Dir.glob('{app,vendor/plugins/mod_**}/**/*.rb') - files.concat(Dir.glob('{app,vendor/plugins/mod_**}/**/*.rhtml')) - files.concat(Dir.glob('{app,vendor/plugins/mod_**}/**/*.erb')) - files.concat(Dir.glob('{app,vendor/plugins/mod_**}/**/*.haml')) - GetText.update_pofiles(TEXT_DOMAIN, files, APP_VERSION) - end -end \ No newline at end of file diff --git a/test/fixtures/articles.yml b/test/fixtures/articles.yml index ab1fb097..e6574b43 100644 --- a/test/fixtures/articles.yml +++ b/test/fixtures/articles.yml @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 20090115232435 +# Schema version: 20090119155930 # # Table name: articles # diff --git a/test/fixtures/suppliers.yml b/test/fixtures/suppliers.yml index b10f55e3..e5a4df54 100644 --- a/test/fixtures/suppliers.yml +++ b/test/fixtures/suppliers.yml @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 20090115232435 +# Schema version: 20090119155930 # # Table name: suppliers # @@ -18,6 +18,7 @@ # note :string(255) # shared_supplier_id :integer(4) # min_order_quantity :string(255) +# deleted_at :datetime # # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html diff --git a/vendor/plugins/acts_as_paranoid/CHANGELOG b/vendor/plugins/acts_as_paranoid/CHANGELOG new file mode 100644 index 00000000..92f4d46f --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/CHANGELOG @@ -0,0 +1,74 @@ +* (4 Oct 2007) + +Update for Edge rails: remove support for legacy #count args + +* (2 Feb 2007) + +Add support for custom primary keys [Jeff Dean] + +* (2 July 2006) + +Add paranoid delete_all implementation [Marshall Roch] + +* (23 May 2006) + +Allow setting of future dates for content expiration. + +* (15 May 2006) + +Added support for dynamic finders + +* (28 Mar 2006) + +Updated for Rails 1.1. I love removing code. + + Refactored #find method + Nested Scopes + +*0.3.1* (20 Dec 2005) + +* took out deleted association code for 'chainsaw butchery of base classes' [sorry Erik Terpstra] +* verified tests pass on Rails 1.0 + +*0.3* (27 Nov 2005) + +* Deleted models will find deleted associations by default now [Erik Terpstra] +* Added :group as valid option for find [Michael Dabney] +* Changed the module namespace to Caboose::Acts::Paranoid + +*0.2.0* (6 Nov 2005) + +* Upgrade to Rails 1.0 RC4. ActiveRecord::Base#constrain has been replaced with scope_with. + +*0.1.7* (22 Oct 2005) + +* Added :with_deleted as a valid option of ActiveRecord::Base#find + +*0.1.6* (25 Sep 2005) + +* Fixed bug where nested constrains would get clobbered after multiple queries + +*0.1.5* (22 Sep 2005) + +* Fixed bug where acts_as_paranoid would clobber other constrains +* Simplified acts_as_paranoid mixin including. + +*0.1.4* (18 Sep 2005) + +* First RubyForge release + +*0.1.3* (18 Sep 2005) + +* ignore multiple calls to acts_as_paranoid on the same model + +*0.1.2* (18 Sep 2005) + +* fixed a bug that kept you from selecting the first deleted record + +*0.1.1* (18 Sep 2005) + +* Fixed bug that kept you from selecting deleted records by ID + +*0.1* (17 Sep 2005) + +* Initial gem diff --git a/vendor/plugins/acts_as_paranoid/MIT-LICENSE b/vendor/plugins/acts_as_paranoid/MIT-LICENSE new file mode 100644 index 00000000..5851fdae --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/MIT-LICENSE @@ -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. \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/README b/vendor/plugins/acts_as_paranoid/README new file mode 100644 index 00000000..0a6f4a07 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/README @@ -0,0 +1,26 @@ += 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. + +== 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 \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/RUNNING_UNIT_TESTS b/vendor/plugins/acts_as_paranoid/RUNNING_UNIT_TESTS new file mode 100644 index 00000000..42f947b6 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/RUNNING_UNIT_TESTS @@ -0,0 +1,41 @@ +== Creating the test database + +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//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 diff --git a/vendor/plugins/acts_as_paranoid/Rakefile b/vendor/plugins/acts_as_paranoid/Rakefile new file mode 100644 index 00000000..5b45a1cf --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/Rakefile @@ -0,0 +1,180 @@ +require 'rubygems' + +Gem::manage_gems + +require 'rake/rdoctask' +require 'rake/packagetask' +require 'rake/gempackagetask' +require 'rake/testtask' +require 'rake/contrib/rubyforgepublisher' + +PKG_NAME = 'acts_as_paranoid' +PKG_VERSION = '0.3.1' +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" +PROD_HOST = "technoweenie@bidwell.textdrive.com" +RUBY_FORGE_PROJECT = 'ar-paranoid' +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 acts_as_paranoid plugin.' +Rake::RDocTask.new do |rdoc| + rdoc.rdoc_dir = 'html' + rdoc.title = "#{PKG_NAME} -- protect your ActiveRecord objects from accidental deletion" + rdoc.options << '--line-numbers --inline-source --accessor cattr_accessor=object' + rdoc.template = "#{ENV['template']}.rb" if ENV['template'] + 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 = "acts_as_paranoid keeps models from actually being deleted by setting a deleted_at field." + s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG RUNNING_UNIT_TESTS) + s.files.delete "acts_as_paranoid_plugin.sqlite.db" + s.files.delete "acts_as_paranoid_plugin.sqlite3.db" + s.require_path = 'lib' + s.autorequire = 'acts_as_paranoid' + s.has_rdoc = true + s.test_files = Dir['test/**/*_test.rb'] + 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 \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/init.rb b/vendor/plugins/acts_as_paranoid/init.rb new file mode 100644 index 00000000..12978e05 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/init.rb @@ -0,0 +1,34 @@ +class << ActiveRecord::Base + def belongs_to_with_deleted(association_id, options = {}) + with_deleted = options.delete :with_deleted + returning belongs_to_without_deleted(association_id, options) do + if with_deleted + reflection = reflect_on_association(association_id) + association_accessor_methods(reflection, Caboose::Acts::BelongsToWithDeletedAssociation) + association_constructor_method(:build, reflection, Caboose::Acts::BelongsToWithDeletedAssociation) + association_constructor_method(:create, reflection, Caboose::Acts::BelongsToWithDeletedAssociation) + end + 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 diff --git a/vendor/plugins/acts_as_paranoid/lib/caboose/acts/belongs_to_with_deleted_association.rb b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/belongs_to_with_deleted_association.rb new file mode 100644 index 00000000..3ac6416c --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/belongs_to_with_deleted_association.rb @@ -0,0 +1,14 @@ +module Caboose # :nodoc: + module Acts # :nodoc: + class BelongsToWithDeletedAssociation < ActiveRecord::Associations::BelongsToAssociation + private + def find_target + @reflection.klass.find_with_deleted( + @owner[@reflection.primary_key_name], + :conditions => conditions, + :include => @reflection.options[:include] + ) + end + end + end +end \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/lib/caboose/acts/has_many_through_without_deleted_association.rb b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/has_many_through_without_deleted_association.rb new file mode 100644 index 00000000..4948a723 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/has_many_through_without_deleted_association.rb @@ -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 \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb new file mode 100644 index 00000000..fe9e0e80 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid.rb @@ -0,0 +1,196 @@ +module Caboose #:nodoc: + module Acts #:nodoc: + # Overrides some basic methods for the current model so that calling #destroy sets a 'deleted_at' field to the current timestamp. + # This assumes the table has a deleted_at date/time field. Most normal model operations will work, but there will be some oddities. + # + # class Widget < ActiveRecord::Base + # acts_as_paranoid + # end + # + # Widget.find(:all) + # # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL + # + # Widget.find(:first, :conditions => ['title = ?', 'test'], :order => 'title') + # # SELECT * FROM widgets WHERE widgets.deleted_at IS NULL AND title = 'test' ORDER BY title LIMIT 1 + # + # Widget.find_with_deleted(:all) + # # 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 + # + # Widget.count + # # SELECT COUNT(*) FROM widgets WHERE widgets.deleted_at IS NULL + # + # Widget.count ['title = ?', 'test'] + # # SELECT COUNT(*) FROM widgets WHERE widgets.deleted_at IS NULL AND title = 'test' + # + # 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' + # + # Widget.delete_all! + # # DELETE FROM widgets + # + # @widget.destroy + # # UPDATE widgets SET deleted_at = '2005-09-17 17:46:36' WHERE id = 1 + # + # @widget.destroy! + # # DELETE FROM widgets WHERE id = 1 + # + module Paranoid + def self.included(base) # :nodoc: + base.extend ClassMethods + end + + module ClassMethods + def acts_as_paranoid(options = {}) + unless paranoid? # don't let AR call this twice + cattr_accessor :deleted_attribute + self.deleted_attribute = options[:with] || :deleted_at + alias_method :destroy_without_callbacks!, :destroy_without_callbacks + class << self + alias_method :find_every_with_deleted, :find_every + alias_method :calculate_with_deleted, :calculate + alias_method :delete_all!, :delete_all + end + end + include InstanceMethods + end + + def paranoid? + self.included_modules.include?(InstanceMethods) + end + end + + module InstanceMethods #:nodoc: + def self.included(base) # :nodoc: + base.extend ClassMethods + end + + module ClassMethods + def find_with_deleted(*args) + options = args.extract_options! + validate_find_options(options) + set_readonly_option!(options) + options[:with_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 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) } + end + + def calculate(*args) + with_deleted_scope { calculate_with_deleted(*args) } + end + + def delete_all(conditions = nil) + self.update_all ["#{self.deleted_attribute} = ?", current_time], conditions + end + + protected + def current_time + default_timezone == :utc ? Time.now.utc : Time.now + end + + def with_deleted_scope(&block) + 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) : + options.delete(:only_deleted) ? + with_only_deleted_scope { find_every_with_deleted(options) } : + with_deleted_scope { find_every_with_deleted(options) } + end + end + + def destroy_without_callbacks + unless new_record? + 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 + + def destroy_with_callbacks! + return false if callback(:before_destroy) == false + result = destroy_without_callbacks! + callback(:after_destroy) + result + end + + def destroy! + transaction { destroy_with_callbacks! } + end + + def deleted? + !!read_attribute(:deleted_at) + end + + def recover! + self.deleted_at = nil + 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 +end diff --git a/vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid_find_wrapper.rb b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid_find_wrapper.rb new file mode 100644 index 00000000..d8cdcacf --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/lib/caboose/acts/paranoid_find_wrapper.rb @@ -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 diff --git a/vendor/plugins/acts_as_paranoid/test/database.yml b/vendor/plugins/acts_as_paranoid/test/database.yml new file mode 100644 index 00000000..cb4e7901 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/database.yml @@ -0,0 +1,18 @@ +sqlite: + :adapter: sqlite + :dbfile: acts_as_paranoid_plugin.sqlite.db +sqlite3: + :adapter: sqlite3 + :dbfile: acts_as_paranoid_plugin.sqlite3.db +postgresql: + :adapter: postgresql + :username: postgres + :password: postgres + :database: acts_as_paranoid_plugin_test + :min_messages: ERROR +mysql: + :adapter: mysql + :host: localhost + :username: rails + :password: + :database: acts_as_paranoid_plugin_test \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/test/fixtures/categories.yml b/vendor/plugins/acts_as_paranoid/test/fixtures/categories.yml new file mode 100644 index 00000000..5d8a3418 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/fixtures/categories.yml @@ -0,0 +1,19 @@ +category_1: + id: 1 + widget_id: 1 + title: 'category 1' +category_2: + id: 2 + widget_id: 1 + title: 'category 2' + deleted_at: '2005-01-01 00:00:00' +category_3: + id: 3 + widget_id: 2 + title: 'category 3' + deleted_at: '2005-01-01 00:00:00' +category_4: + id: 4 + widget_id: 2 + title: 'category 4' + deleted_at: '2005-01-01 00:00:00' \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/test/fixtures/categories_widgets.yml b/vendor/plugins/acts_as_paranoid/test/fixtures/categories_widgets.yml new file mode 100644 index 00000000..fcd7eabc --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/fixtures/categories_widgets.yml @@ -0,0 +1,12 @@ +cw_1: + category_id: 1 + widget_id: 1 +cw_2: + category_id: 2 + widget_id: 1 +cw_3: + category_id: 3 + widget_id: 2 +cw_4: + category_id: 4 + widget_id: 2 \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/test/fixtures/taggings.yml b/vendor/plugins/acts_as_paranoid/test/fixtures/taggings.yml new file mode 100644 index 00000000..36ced622 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/fixtures/taggings.yml @@ -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 diff --git a/vendor/plugins/acts_as_paranoid/test/fixtures/tags.yml b/vendor/plugins/acts_as_paranoid/test/fixtures/tags.yml new file mode 100644 index 00000000..8617beba --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/fixtures/tags.yml @@ -0,0 +1,6 @@ +tag_1: + id: 1 + name: 'tag 1' +tag_2: + id: 2 + name: 'tag 1' diff --git a/vendor/plugins/acts_as_paranoid/test/fixtures/widgets.yml b/vendor/plugins/acts_as_paranoid/test/fixtures/widgets.yml new file mode 100644 index 00000000..5a8b9333 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/fixtures/widgets.yml @@ -0,0 +1,8 @@ +widget_1: + id: 1 + title: 'widget 1' +widget_2: + id: 2 + title: 'deleted widget 2' + deleted_at: '2005-01-01 00:00:00' + category_id: 3 diff --git a/vendor/plugins/acts_as_paranoid/test/paranoid_test.rb b/vendor/plugins/acts_as_paranoid/test/paranoid_test.rb new file mode 100644 index 00000000..69e6fbea --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/paranoid_test.rb @@ -0,0 +1,287 @@ +require File.join(File.dirname(__FILE__), 'test_helper') + +class Widget < ActiveRecord::Base + acts_as_paranoid + has_many :categories, :dependent => :destroy + 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 + belongs_to :widget + belongs_to :any_widget, :class_name => 'Widget', :foreign_key => 'widget_id', :with_deleted => true + acts_as_paranoid + + def self.search(name, options = {}) + find :all, options.merge(:conditions => ['LOWER(title) LIKE ?', "%#{name.to_s.downcase}%"]) + end + + def self.search_with_deleted(name, options = {}) + find_with_deleted :all, options.merge(:conditions => ['LOWER(title) LIKE ?', "%#{name.to_s.downcase}%"]) + 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, :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 + + def test_should_set_deleted_at + assert_equal 1, Widget.count + assert_equal 1, Category.count + widgets(:widget_1).destroy + assert_equal 0, Widget.count + assert_equal 0, Category.count + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + assert_equal 4, Category.calculate_with_deleted(:count, :all) + end + + def test_should_destroy + assert_equal 1, Widget.count + assert_equal 1, Category.count + 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) + end + + def test_should_delete_all + assert_equal 1, Widget.count + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + assert_equal 1, Category.count + Widget.delete_all + assert_equal 0, Widget.count + # delete_all doesn't call #destroy, so the dependent callback never fires + assert_equal 1, Category.count + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + end + + def test_should_delete_all_with_conditions + assert_equal 1, Widget.count + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + Widget.delete_all("id < 3") + assert_equal 0, Widget.count + assert_equal 2, Widget.calculate_with_deleted(:count, :all) + end + + def test_should_delete_all2 + assert_equal 1, Category.count + assert_equal 4, Category.calculate_with_deleted(:count, :all) + Category.delete_all! + assert_equal 0, Category.count + assert_equal 0, Category.calculate_with_deleted(:count, :all) + end + + def test_should_delete_all_with_conditions2 + assert_equal 1, Category.count + assert_equal 4, Category.calculate_with_deleted(:count, :all) + Category.delete_all!("id < 3") + assert_equal 0, Category.count + assert_equal 2, Category.calculate_with_deleted(:count, :all) + end + + def test_should_not_count_deleted + 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 + assert_equal [widgets(:widget_1)], Widget.find(:all) + assert_equal [1, 2], Widget.find_with_deleted(:all, :order => 'id').collect { |w| w.id } + end + + def test_should_not_find_deleted_has_many_associations + assert_equal 1, widgets(:widget_1).categories.size + assert_equal [categories(:category_1)], widgets(:widget_1).categories + end + + def test_should_not_find_deleted_habtm_associations + assert_equal 1, widgets(:widget_1).habtm_categories.size + 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 + + def test_should_find_belongs_to_assocation_with_deleted + assert_equal Widget.find_with_deleted(2), Category.find_with_deleted(3).any_widget + end + + def test_should_find_first_with_deleted + assert_equal widgets(:widget_1), Widget.find(:first) + assert_equal 2, Widget.find_with_deleted(:first, :order => 'id desc').id + end + + def test_should_find_single_id + assert Widget.find(1) + assert Widget.find_with_deleted(2) + assert_raises(ActiveRecord::RecordNotFound) { Widget.find(2) } + end + + def test_should_find_multiple_ids + assert_equal [1,2], Widget.find_with_deleted(1,2).sort_by { |w| w.id }.collect { |w| w.id } + assert_equal [1,2], Widget.find_with_deleted([1,2]).sort_by { |w| w.id }.collect { |w| w.id } + assert_raises(ActiveRecord::RecordNotFound) { Widget.find(1,2) } + end + + def test_should_ignore_multiple_includes + Widget.class_eval { acts_as_paranoid } + assert Widget.find(1) + end + + def test_should_not_override_scopes_when_counting + assert_equal 1, Widget.send(:with_scope, :find => { :conditions => "title = 'widget 1'" }) { Widget.count } + assert_equal 0, Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.count } + assert_equal 1, Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.calculate_with_deleted(:count, :all) } + end + + def test_should_not_override_scopes_when_finding + assert_equal [1], Widget.send(:with_scope, :find => { :conditions => "title = 'widget 1'" }) { Widget.find(:all) }.ids + assert_equal [], Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.find(:all) }.ids + assert_equal [2], Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) { Widget.find_with_deleted(:all) }.ids + end + + def test_should_allow_multiple_scoped_calls_when_finding + Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) do + assert_equal [2], Widget.find_with_deleted(:all).ids + assert_equal [2], Widget.find_with_deleted(:all).ids, "clobbers the constrain on the unmodified find" + assert_equal [], Widget.find(:all).ids + assert_equal [], Widget.find(:all).ids, 'clobbers the constrain on a paranoid find' + end + end + + def test_should_allow_multiple_scoped_calls_when_counting + Widget.send(:with_scope, :find => { :conditions => "title = 'deleted widget 2'" }) do + assert_equal 1, Widget.calculate_with_deleted(:count, :all) + assert_equal 1, Widget.calculate_with_deleted(:count, :all), "clobbers the constrain on the unmodified find" + assert_equal 0, Widget.count + assert_equal 0, Widget.count, 'clobbers the constrain on a paranoid find' + end + end + + def test_should_give_paranoid_status + assert Widget.paranoid? + assert !NonParanoidAndroid.paranoid? + end + + def test_should_give_record_status + assert_equal false, Widget.find(1).deleted? + Widget.find(1).destroy + assert Widget.find_with_deleted(1).deleted? + end + + def test_should_find_deleted_has_many_assocations_on_deleted_records_by_default + w = Widget.find_with_deleted 2 + assert_equal 2, w.categories.find_with_deleted(:all).length + assert_equal 2, w.categories.find_with_deleted(:all).size + end + + def test_should_find_deleted_habtm_assocations_on_deleted_records_by_default + w = Widget.find_with_deleted 2 + assert_equal 2, w.habtm_categories.find_with_deleted(:all).length + assert_equal 2, w.habtm_categories.find_with_deleted(:all).size + end + + def test_dynamic_finders + assert Widget.find_by_id(1) + assert_nil Widget.find_by_id(2) + end + + def test_custom_finder_methods + w = Widget.find_with_deleted(:all).inject({}) { |all, w| all.merge(w.id => w) } + assert_equal [1], Category.search('c').ids + assert_equal [1,2,3,4], Category.search_with_deleted('c', :order => 'id').ids + assert_equal [1], widgets(:widget_1).categories.search('c').collect(&:id) + assert_equal [1,2], widgets(:widget_1).categories.search_with_deleted('c').ids + 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 + def ids + collect &:id + end +end diff --git a/vendor/plugins/acts_as_paranoid/test/schema.rb b/vendor/plugins/acts_as_paranoid/test/schema.rb new file mode 100644 index 00000000..e955bae9 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/schema.rb @@ -0,0 +1,30 @@ +ActiveRecord::Schema.define(:version => 1) do + + create_table :widgets, :force => true do |t| + t.column :title, :string, :limit => 50 + t.column :category_id, :integer + t.column :deleted_at, :timestamp + end + + create_table :categories, :force => true do |t| + t.column :widget_id, :integer + t.column :title, :string, :limit => 50 + t.column :deleted_at, :timestamp + end + + create_table :categories_widgets, :force => true, :id => false do |t| + 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 \ No newline at end of file diff --git a/vendor/plugins/acts_as_paranoid/test/test_helper.rb b/vendor/plugins/acts_as_paranoid/test/test_helper.rb new file mode 100644 index 00000000..6dfc9058 --- /dev/null +++ b/vendor/plugins/acts_as_paranoid/test/test_helper.rb @@ -0,0 +1,33 @@ +$:.unshift(File.dirname(__FILE__) + '/../lib') + +require 'test/unit' +require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb')) +require 'rubygems' +require 'active_record/fixtures' + +config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) +ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log") +ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite']) + +load(File.dirname(__FILE__) + "/schema.rb") + +Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/" +$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path) + +class Test::Unit::TestCase #:nodoc: + def create_fixtures(*table_names) + if block_given? + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield } + else + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) + end + end + + # 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