From 92c83721a74b513f36cc4e9602b6e1408852e41a Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Tue, 7 Aug 2012 10:53:15 +1000 Subject: [PATCH] Add vendored gems --- vendor/gems/database_cleaner/.gitignore | 11 + vendor/gems/database_cleaner/.rbenv-version | 1 + vendor/gems/database_cleaner/.rspec | 4 + vendor/gems/database_cleaner/.rvmrc | 1 + vendor/gems/database_cleaner/.travis.yml | 13 + vendor/gems/database_cleaner/Gemfile | 43 + vendor/gems/database_cleaner/Gemfile.lock | 231 +++ vendor/gems/database_cleaner/Guardfile | 9 + vendor/gems/database_cleaner/History.txt | 221 +++ vendor/gems/database_cleaner/LICENSE | 20 + vendor/gems/database_cleaner/README.textile | 245 ++++ vendor/gems/database_cleaner/Rakefile | 55 + vendor/gems/database_cleaner/TODO | 3 + vendor/gems/database_cleaner/VERSION.yml | 5 + vendor/gems/database_cleaner/cucumber.yml | 1 + .../database_cleaner/database_cleaner.gemspec | 149 ++ .../database_cleaner/db/sample.config.yml | 26 + vendor/gems/database_cleaner/examples/Gemfile | 1 + .../database_cleaner/examples/Gemfile.lock | 1 + .../examples/config/database.yml.example | 8 + .../examples/db/sqlite_databases_go_here | 0 .../examples/features/example.feature | 11 + .../features/example_multiple_db.feature | 23 + .../features/example_multiple_orm.feature | 22 + .../step_definitions/activerecord_steps.rb | 31 + .../step_definitions/couchpotato_steps.rb | 31 + .../step_definitions/datamapper_steps.rb | 37 + .../step_definitions/mongoid_steps.rb | 23 + .../step_definitions/mongomapper_steps.rb | 31 + .../step_definitions/translation_steps.rb | 55 + .../examples/features/support/env.rb | 62 + .../examples/lib/activerecord_models.rb | 41 + .../examples/lib/couchpotato_models.rb | 61 + .../examples/lib/datamapper_models.rb | 50 + .../examples/lib/mongoid_models.rb | 49 + .../examples/lib/mongomapper_models.rb | 51 + .../features/cleaning.feature | 22 + .../cleaning_default_strategy.feature | 19 + .../features/cleaning_multiple_dbs.feature | 21 + .../features/cleaning_multiple_orms.feature | 29 + .../database_cleaner_steps.rb | 32 + .../database_cleaner/features/support/env.rb | 7 + .../features/support/feature_runner.rb | 39 + .../database_cleaner/lib/database_cleaner.rb | 3 + .../database_cleaner/active_record/base.rb | 53 + .../active_record/deletion.rb | 67 + .../active_record/transaction.rb | 31 + .../active_record/truncation.rb | 256 ++++ .../lib/database_cleaner/base.rb | 138 ++ .../lib/database_cleaner/configuration.rb | 96 ++ .../lib/database_cleaner/couch_potato/base.rb | 7 + .../couch_potato/truncation.rb | 28 + .../lib/database_cleaner/cucumber.rb | 11 + .../lib/database_cleaner/data_mapper/base.rb | 21 + .../data_mapper/transaction.rb | 28 + .../data_mapper/truncation.rb | 175 +++ .../lib/database_cleaner/generic/base.rb | 20 + .../database_cleaner/generic/transaction.rb | 11 + .../database_cleaner/generic/truncation.rb | 39 + .../lib/database_cleaner/mongo/truncation.rb | 22 + .../lib/database_cleaner/mongo_mapper/base.rb | 20 + .../mongo_mapper/truncation.rb | 19 + .../lib/database_cleaner/mongoid/base.rb | 20 + .../database_cleaner/mongoid/truncation.rb | 37 + .../lib/database_cleaner/moped/truncation.rb | 25 + .../lib/database_cleaner/null_strategy.rb | 15 + .../lib/database_cleaner/sequel/base.rb | 22 + .../database_cleaner/sequel/transaction.rb | 25 + .../lib/database_cleaner/sequel/truncation.rb | 50 + .../active_record/base_spec.rb | 144 ++ .../active_record/transaction_spec.rb | 77 + .../active_record/truncation/mysql2_spec.rb | 40 + .../active_record/truncation/mysql_spec.rb | 40 + .../truncation/postgresql_spec.rb | 48 + .../truncation/shared_fast_truncation.rb | 40 + .../active_record/truncation_spec.rb | 145 ++ .../spec/database_cleaner/base_spec.rb | 493 +++++++ .../database_cleaner/configuration_spec.rb | 294 ++++ .../couch_potato/truncation_spec.rb | 41 + .../database_cleaner/data_mapper/base_spec.rb | 30 + .../data_mapper/transaction_spec.rb | 23 + .../data_mapper/truncation_spec.rb | 11 + .../database_cleaner/generic/base_spec.rb | 22 + .../generic/truncation_spec.rb | 108 ++ .../mongo_mapper/base_spec.rb | 33 + .../mongo_mapper/mongo_examples.rb | 8 + .../mongo_mapper/truncation_spec.rb | 74 + .../spec/database_cleaner/sequel/base_spec.rb | 32 + .../sequel/transaction_spec.rb | 21 + .../sequel/truncation_spec.rb | 13 + .../spec/database_cleaner/shared_strategy.rb | 13 + vendor/gems/database_cleaner/spec/rcov.opts | 1 + .../gems/database_cleaner/spec/spec_helper.rb | 21 + .../support/active_record/database_setup.rb | 4 + .../support/active_record/mysql2_setup.rb | 38 + .../spec/support/active_record/mysql_setup.rb | 38 + .../support/active_record/postgresql_setup.rb | 41 + .../support/active_record/schema_setup.rb | 10 + vendor/gems/orm_adapter/.gitignore | 15 + vendor/gems/orm_adapter/.rspec | 1 + vendor/gems/orm_adapter/.travis.yml | 4 + vendor/gems/orm_adapter/Gemfile | 3 + .../gems/orm_adapter/Gemfile.lock.development | 129 ++ vendor/gems/orm_adapter/History.txt | 58 + vendor/gems/orm_adapter/LICENSE | 20 + vendor/gems/orm_adapter/README.rdoc | 71 + vendor/gems/orm_adapter/Rakefile | 37 + vendor/gems/orm_adapter/lib/orm_adapter.rb | 15 + .../lib/orm_adapter/adapters/active_record.rb | 76 + .../lib/orm_adapter/adapters/data_mapper.rb | 57 + .../lib/orm_adapter/adapters/mongo_mapper.rb | 65 + .../lib/orm_adapter/adapters/mongoid.rb | 63 + .../gems/orm_adapter/lib/orm_adapter/base.rb | 127 ++ .../orm_adapter/lib/orm_adapter/to_adapter.rb | 14 + .../orm_adapter/lib/orm_adapter/version.rb | 3 + vendor/gems/orm_adapter/orm_adapter.gemspec | 35 + .../adapters/active_record_spec.rb | 62 + .../orm_adapter/adapters/data_mapper_spec.rb | 47 + .../orm_adapter/adapters/mongo_mapper_spec.rb | 41 + .../spec/orm_adapter/adapters/mongoid_spec.rb | 39 + .../orm_adapter/spec/orm_adapter/base_spec.rb | 81 ++ .../spec/orm_adapter/example_app_shared.rb | 240 ++++ .../gems/orm_adapter/spec/orm_adapter_spec.rb | 13 + vendor/gems/orm_adapter/spec/spec_helper.rb | 15 + vendor/gems/paperclip/.gitignore | 24 + vendor/gems/paperclip/.travis.yml | 16 + vendor/gems/paperclip/Appraisals | 14 + vendor/gems/paperclip/CONTRIBUTING.md | 70 + vendor/gems/paperclip/Gemfile | 7 + vendor/gems/paperclip/LICENSE | 26 + vendor/gems/paperclip/NEWS | 178 +++ vendor/gems/paperclip/README.md | 567 ++++++++ vendor/gems/paperclip/RUNNING_TESTS.md | 4 + vendor/gems/paperclip/Rakefile | 46 + vendor/gems/paperclip/UPGRADING | 14 + .../paperclip/cucumber/paperclip_steps.rb | 6 + .../features/basic_integration.feature | 68 + .../gems/paperclip/features/migration.feature | 94 ++ .../paperclip/features/rake_tasks.feature | 63 + .../step_definitions/attachment_steps.rb | 102 ++ .../features/step_definitions/html_steps.rb | 15 + .../features/step_definitions/rails_steps.rb | 138 ++ .../features/step_definitions/s3_steps.rb | 14 + .../features/step_definitions/web_steps.rb | 209 +++ vendor/gems/paperclip/features/support/env.rb | 11 + .../paperclip/features/support/fakeweb.rb | 10 + .../features/support/file_helpers.rb | 24 + .../features/support/fixtures/boot_config.txt | 15 + .../features/support/fixtures/gemfile.txt | 5 + .../support/fixtures/preinitializer.txt | 20 + .../gems/paperclip/features/support/paths.rb | 28 + .../gems/paperclip/features/support/rails.rb | 46 + .../paperclip/features/support/selectors.rb | 19 + vendor/gems/paperclip/gemfiles/3.0.gemfile | 11 + vendor/gems/paperclip/gemfiles/3.1.gemfile | 11 + vendor/gems/paperclip/gemfiles/3.2.gemfile | 11 + .../paperclip/lib/generators/paperclip/USAGE | 8 + .../paperclip/paperclip_generator.rb | 32 + .../templates/paperclip_migration.rb.erb | 15 + vendor/gems/paperclip/lib/paperclip.rb | 226 +++ .../paperclip/lib/paperclip/attachment.rb | 478 ++++++ .../lib/paperclip/attachment_options.rb | 9 + .../gems/paperclip/lib/paperclip/callbacks.rb | 30 + .../lib/paperclip/content_type_detector.rb | 67 + vendor/gems/paperclip/lib/paperclip/errors.rb | 27 + .../gems/paperclip/lib/paperclip/geometry.rb | 155 ++ vendor/gems/paperclip/lib/paperclip/glue.rb | 18 + .../gems/paperclip/lib/paperclip/helpers.rb | 59 + .../lib/paperclip/instance_methods.rb | 35 + .../paperclip/lib/paperclip/interpolations.rb | 180 +++ .../paperclip/io_adapters/abstract_adapter.rb | 31 + .../io_adapters/attachment_adapter.rb | 36 + .../lib/paperclip/io_adapters/file_adapter.rb | 22 + .../paperclip/io_adapters/identity_adapter.rb | 12 + .../lib/paperclip/io_adapters/nil_adapter.rb | 34 + .../lib/paperclip/io_adapters/registry.rb | 32 + .../paperclip/io_adapters/stringio_adapter.rb | 36 + .../io_adapters/uploaded_file_adapter.rb | 26 + .../lib/paperclip/io_adapters/uri_adapter.rb | 42 + .../paperclip/lib/paperclip/locales/en.yml | 17 + vendor/gems/paperclip/lib/paperclip/logger.rb | 21 + .../gems/paperclip/lib/paperclip/matchers.rb | 64 + .../matchers/have_attached_file_matcher.rb | 57 + ...alidate_attachment_content_type_matcher.rb | 100 ++ .../validate_attachment_presence_matcher.rb | 54 + .../validate_attachment_size_matcher.rb | 95 ++ .../paperclip/missing_attachment_styles.rb | 84 ++ .../gems/paperclip/lib/paperclip/processor.rb | 85 ++ .../gems/paperclip/lib/paperclip/railtie.rb | 31 + vendor/gems/paperclip/lib/paperclip/schema.rb | 75 + .../gems/paperclip/lib/paperclip/storage.rb | 3 + .../lib/paperclip/storage/filesystem.rb | 77 + .../paperclip/lib/paperclip/storage/fog.rb | 200 +++ .../paperclip/lib/paperclip/storage/s3.rb | 366 +++++ vendor/gems/paperclip/lib/paperclip/style.rb | 103 ++ .../gems/paperclip/lib/paperclip/tempfile.rb | 43 + .../lib/paperclip/tempfile_factory.rb | 21 + .../gems/paperclip/lib/paperclip/thumbnail.rb | 113 ++ .../paperclip/lib/paperclip/url_generator.rb | 64 + .../paperclip/lib/paperclip/validators.rb | 46 + .../attachment_content_type_validator.rb | 54 + .../attachment_presence_validator.rb | 26 + .../validators/attachment_size_validator.rb | 102 ++ .../gems/paperclip/lib/paperclip/version.rb | 3 + .../gems/paperclip/lib/tasks/paperclip.rake | 93 ++ vendor/gems/paperclip/paperclip.gemspec | 53 + .../paperclip/shoulda_macros/paperclip.rb | 124 ++ .../paperclip/test/attachment_options_test.rb | 27 + vendor/gems/paperclip/test/attachment_test.rb | 1277 +++++++++++++++++ .../test/content_type_detector_test.rb | 40 + vendor/gems/paperclip/test/database.yml | 4 + vendor/gems/paperclip/test/fixtures/12k.png | Bin 0 -> 12093 bytes vendor/gems/paperclip/test/fixtures/50x50.png | Bin 0 -> 1615 bytes vendor/gems/paperclip/test/fixtures/5k.png | Bin 0 -> 4456 bytes vendor/gems/paperclip/test/fixtures/animated | Bin 0 -> 8238 bytes .../gems/paperclip/test/fixtures/animated.gif | Bin 0 -> 8238 bytes .../paperclip/test/fixtures/animated.unknown | Bin 0 -> 8238 bytes vendor/gems/paperclip/test/fixtures/bad.png | 1 + vendor/gems/paperclip/test/fixtures/fog.yml | 8 + vendor/gems/paperclip/test/fixtures/s3.yml | 8 + .../paperclip/test/fixtures/spaced file.png | Bin 0 -> 4456 bytes vendor/gems/paperclip/test/fixtures/text.txt | 1 + .../gems/paperclip/test/fixtures/twopage.pdf | Bin 0 -> 8775 bytes .../paperclip/test/fixtures/uppercase.PNG | Bin 0 -> 4456 bytes vendor/gems/paperclip/test/generator_test.rb | 80 ++ vendor/gems/paperclip/test/geometry_test.rb | 225 +++ vendor/gems/paperclip/test/helper.rb | 187 +++ .../gems/paperclip/test/integration_test.rb | 677 +++++++++ .../paperclip/test/interpolations_test.rb | 238 +++ .../test/io_adapters/abstract_adapter_test.rb | 43 + .../io_adapters/attachment_adapter_test.rb | 113 ++ .../test/io_adapters/file_adapter_test.rb | 100 ++ .../test/io_adapters/identity_adapter_test.rb | 8 + .../test/io_adapters/nil_adapter_test.rb | 25 + .../test/io_adapters/registry_test.rb | 32 + .../test/io_adapters/stringio_adapter_test.rb | 51 + .../io_adapters/uploaded_file_adapter_test.rb | 99 ++ .../test/io_adapters/uri_adapter_test.rb | 82 ++ .../have_attached_file_matcher_test.rb | 24 + ...te_attachment_content_type_matcher_test.rb | 110 ++ ...lidate_attachment_presence_matcher_test.rb | 47 + .../validate_attachment_size_matcher_test.rb | 86 ++ ...aperclip_missing_attachment_styles_test.rb | 94 ++ vendor/gems/paperclip/test/paperclip_test.rb | 241 ++++ vendor/gems/paperclip/test/processor_test.rb | 26 + vendor/gems/paperclip/test/schema_test.rb | 200 +++ .../paperclip/test/storage/filesystem_test.rb | 71 + .../gems/paperclip/test/storage/fog_test.rb | 353 +++++ .../paperclip/test/storage/s3_live_test.rb | 179 +++ vendor/gems/paperclip/test/storage/s3_test.rb | 1196 +++++++++++++++ vendor/gems/paperclip/test/style_test.rb | 209 +++ .../paperclip/test/support/mock_attachment.rb | 22 + .../test/support/mock_interpolator.rb | 24 + .../gems/paperclip/test/support/mock_model.rb | 2 + .../support/mock_url_generator_builder.rb | 27 + .../paperclip/test/tempfile_factory_test.rb | 13 + vendor/gems/paperclip/test/thumbnail_test.rb | 446 ++++++ .../gems/paperclip/test/url_generator_test.rb | 187 +++ .../attachment_content_type_validator_test.rb | 192 +++ .../attachment_presence_validator_test.rb | 85 ++ .../attachment_size_validator_test.rb | 207 +++ vendor/gems/paperclip/test/validators_test.rb | 25 + 262 files changed, 19330 insertions(+) create mode 100644 vendor/gems/database_cleaner/.gitignore create mode 100644 vendor/gems/database_cleaner/.rbenv-version create mode 100644 vendor/gems/database_cleaner/.rspec create mode 100644 vendor/gems/database_cleaner/.rvmrc create mode 100644 vendor/gems/database_cleaner/.travis.yml create mode 100755 vendor/gems/database_cleaner/Gemfile create mode 100644 vendor/gems/database_cleaner/Gemfile.lock create mode 100644 vendor/gems/database_cleaner/Guardfile create mode 100644 vendor/gems/database_cleaner/History.txt create mode 100644 vendor/gems/database_cleaner/LICENSE create mode 100644 vendor/gems/database_cleaner/README.textile create mode 100644 vendor/gems/database_cleaner/Rakefile create mode 100644 vendor/gems/database_cleaner/TODO create mode 100644 vendor/gems/database_cleaner/VERSION.yml create mode 100644 vendor/gems/database_cleaner/cucumber.yml create mode 100644 vendor/gems/database_cleaner/database_cleaner.gemspec create mode 100644 vendor/gems/database_cleaner/db/sample.config.yml create mode 120000 vendor/gems/database_cleaner/examples/Gemfile create mode 120000 vendor/gems/database_cleaner/examples/Gemfile.lock create mode 100644 vendor/gems/database_cleaner/examples/config/database.yml.example create mode 100644 vendor/gems/database_cleaner/examples/db/sqlite_databases_go_here create mode 100644 vendor/gems/database_cleaner/examples/features/example.feature create mode 100644 vendor/gems/database_cleaner/examples/features/example_multiple_db.feature create mode 100644 vendor/gems/database_cleaner/examples/features/example_multiple_orm.feature create mode 100644 vendor/gems/database_cleaner/examples/features/step_definitions/activerecord_steps.rb create mode 100644 vendor/gems/database_cleaner/examples/features/step_definitions/couchpotato_steps.rb create mode 100644 vendor/gems/database_cleaner/examples/features/step_definitions/datamapper_steps.rb create mode 100644 vendor/gems/database_cleaner/examples/features/step_definitions/mongoid_steps.rb create mode 100644 vendor/gems/database_cleaner/examples/features/step_definitions/mongomapper_steps.rb create mode 100644 vendor/gems/database_cleaner/examples/features/step_definitions/translation_steps.rb create mode 100644 vendor/gems/database_cleaner/examples/features/support/env.rb create mode 100644 vendor/gems/database_cleaner/examples/lib/activerecord_models.rb create mode 100644 vendor/gems/database_cleaner/examples/lib/couchpotato_models.rb create mode 100644 vendor/gems/database_cleaner/examples/lib/datamapper_models.rb create mode 100644 vendor/gems/database_cleaner/examples/lib/mongoid_models.rb create mode 100644 vendor/gems/database_cleaner/examples/lib/mongomapper_models.rb create mode 100644 vendor/gems/database_cleaner/features/cleaning.feature create mode 100644 vendor/gems/database_cleaner/features/cleaning_default_strategy.feature create mode 100644 vendor/gems/database_cleaner/features/cleaning_multiple_dbs.feature create mode 100644 vendor/gems/database_cleaner/features/cleaning_multiple_orms.feature create mode 100644 vendor/gems/database_cleaner/features/step_definitions/database_cleaner_steps.rb create mode 100644 vendor/gems/database_cleaner/features/support/env.rb create mode 100644 vendor/gems/database_cleaner/features/support/feature_runner.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/active_record/base.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/active_record/deletion.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/active_record/transaction.rb create mode 100755 vendor/gems/database_cleaner/lib/database_cleaner/active_record/truncation.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/base.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/configuration.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/couch_potato/base.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/couch_potato/truncation.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/cucumber.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/base.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/transaction.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/truncation.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/generic/base.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/generic/transaction.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/generic/truncation.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/mongo/truncation.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/mongo_mapper/base.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/mongo_mapper/truncation.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/mongoid/base.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/mongoid/truncation.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/moped/truncation.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/null_strategy.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/sequel/base.rb create mode 100644 vendor/gems/database_cleaner/lib/database_cleaner/sequel/transaction.rb create mode 100755 vendor/gems/database_cleaner/lib/database_cleaner/sequel/truncation.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/active_record/base_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/active_record/transaction_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/mysql2_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/mysql_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/postgresql_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/shared_fast_truncation.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/base_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/configuration_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/couch_potato/truncation_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/base_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/transaction_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/truncation_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/generic/base_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/generic/truncation_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/base_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/mongo_examples.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/truncation_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/sequel/base_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/sequel/transaction_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/sequel/truncation_spec.rb create mode 100644 vendor/gems/database_cleaner/spec/database_cleaner/shared_strategy.rb create mode 100644 vendor/gems/database_cleaner/spec/rcov.opts create mode 100644 vendor/gems/database_cleaner/spec/spec_helper.rb create mode 100644 vendor/gems/database_cleaner/spec/support/active_record/database_setup.rb create mode 100644 vendor/gems/database_cleaner/spec/support/active_record/mysql2_setup.rb create mode 100644 vendor/gems/database_cleaner/spec/support/active_record/mysql_setup.rb create mode 100644 vendor/gems/database_cleaner/spec/support/active_record/postgresql_setup.rb create mode 100644 vendor/gems/database_cleaner/spec/support/active_record/schema_setup.rb create mode 100644 vendor/gems/orm_adapter/.gitignore create mode 100644 vendor/gems/orm_adapter/.rspec create mode 100644 vendor/gems/orm_adapter/.travis.yml create mode 100644 vendor/gems/orm_adapter/Gemfile create mode 100644 vendor/gems/orm_adapter/Gemfile.lock.development create mode 100644 vendor/gems/orm_adapter/History.txt create mode 100644 vendor/gems/orm_adapter/LICENSE create mode 100644 vendor/gems/orm_adapter/README.rdoc create mode 100644 vendor/gems/orm_adapter/Rakefile create mode 100644 vendor/gems/orm_adapter/lib/orm_adapter.rb create mode 100644 vendor/gems/orm_adapter/lib/orm_adapter/adapters/active_record.rb create mode 100644 vendor/gems/orm_adapter/lib/orm_adapter/adapters/data_mapper.rb create mode 100644 vendor/gems/orm_adapter/lib/orm_adapter/adapters/mongo_mapper.rb create mode 100644 vendor/gems/orm_adapter/lib/orm_adapter/adapters/mongoid.rb create mode 100644 vendor/gems/orm_adapter/lib/orm_adapter/base.rb create mode 100644 vendor/gems/orm_adapter/lib/orm_adapter/to_adapter.rb create mode 100644 vendor/gems/orm_adapter/lib/orm_adapter/version.rb create mode 100644 vendor/gems/orm_adapter/orm_adapter.gemspec create mode 100644 vendor/gems/orm_adapter/spec/orm_adapter/adapters/active_record_spec.rb create mode 100644 vendor/gems/orm_adapter/spec/orm_adapter/adapters/data_mapper_spec.rb create mode 100644 vendor/gems/orm_adapter/spec/orm_adapter/adapters/mongo_mapper_spec.rb create mode 100644 vendor/gems/orm_adapter/spec/orm_adapter/adapters/mongoid_spec.rb create mode 100644 vendor/gems/orm_adapter/spec/orm_adapter/base_spec.rb create mode 100644 vendor/gems/orm_adapter/spec/orm_adapter/example_app_shared.rb create mode 100644 vendor/gems/orm_adapter/spec/orm_adapter_spec.rb create mode 100644 vendor/gems/orm_adapter/spec/spec_helper.rb create mode 100644 vendor/gems/paperclip/.gitignore create mode 100644 vendor/gems/paperclip/.travis.yml create mode 100644 vendor/gems/paperclip/Appraisals create mode 100644 vendor/gems/paperclip/CONTRIBUTING.md create mode 100644 vendor/gems/paperclip/Gemfile create mode 100644 vendor/gems/paperclip/LICENSE create mode 100644 vendor/gems/paperclip/NEWS create mode 100644 vendor/gems/paperclip/README.md create mode 100644 vendor/gems/paperclip/RUNNING_TESTS.md create mode 100644 vendor/gems/paperclip/Rakefile create mode 100644 vendor/gems/paperclip/UPGRADING create mode 100644 vendor/gems/paperclip/cucumber/paperclip_steps.rb create mode 100644 vendor/gems/paperclip/features/basic_integration.feature create mode 100644 vendor/gems/paperclip/features/migration.feature create mode 100644 vendor/gems/paperclip/features/rake_tasks.feature create mode 100644 vendor/gems/paperclip/features/step_definitions/attachment_steps.rb create mode 100644 vendor/gems/paperclip/features/step_definitions/html_steps.rb create mode 100644 vendor/gems/paperclip/features/step_definitions/rails_steps.rb create mode 100644 vendor/gems/paperclip/features/step_definitions/s3_steps.rb create mode 100644 vendor/gems/paperclip/features/step_definitions/web_steps.rb create mode 100644 vendor/gems/paperclip/features/support/env.rb create mode 100644 vendor/gems/paperclip/features/support/fakeweb.rb create mode 100644 vendor/gems/paperclip/features/support/file_helpers.rb create mode 100644 vendor/gems/paperclip/features/support/fixtures/boot_config.txt create mode 100644 vendor/gems/paperclip/features/support/fixtures/gemfile.txt create mode 100644 vendor/gems/paperclip/features/support/fixtures/preinitializer.txt create mode 100644 vendor/gems/paperclip/features/support/paths.rb create mode 100644 vendor/gems/paperclip/features/support/rails.rb create mode 100644 vendor/gems/paperclip/features/support/selectors.rb create mode 100644 vendor/gems/paperclip/gemfiles/3.0.gemfile create mode 100644 vendor/gems/paperclip/gemfiles/3.1.gemfile create mode 100644 vendor/gems/paperclip/gemfiles/3.2.gemfile create mode 100644 vendor/gems/paperclip/lib/generators/paperclip/USAGE create mode 100644 vendor/gems/paperclip/lib/generators/paperclip/paperclip_generator.rb create mode 100644 vendor/gems/paperclip/lib/generators/paperclip/templates/paperclip_migration.rb.erb create mode 100644 vendor/gems/paperclip/lib/paperclip.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/attachment.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/attachment_options.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/callbacks.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/content_type_detector.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/errors.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/geometry.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/glue.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/helpers.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/instance_methods.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/interpolations.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/io_adapters/abstract_adapter.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/io_adapters/attachment_adapter.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/io_adapters/file_adapter.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/io_adapters/identity_adapter.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/io_adapters/nil_adapter.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/io_adapters/registry.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/io_adapters/stringio_adapter.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/io_adapters/uploaded_file_adapter.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/io_adapters/uri_adapter.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/locales/en.yml create mode 100644 vendor/gems/paperclip/lib/paperclip/logger.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/matchers.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/missing_attachment_styles.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/processor.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/railtie.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/schema.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/storage.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/storage/filesystem.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/storage/fog.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/storage/s3.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/style.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/tempfile.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/tempfile_factory.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/thumbnail.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/url_generator.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/validators.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/validators/attachment_content_type_validator.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/validators/attachment_presence_validator.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/validators/attachment_size_validator.rb create mode 100644 vendor/gems/paperclip/lib/paperclip/version.rb create mode 100644 vendor/gems/paperclip/lib/tasks/paperclip.rake create mode 100644 vendor/gems/paperclip/paperclip.gemspec create mode 100644 vendor/gems/paperclip/shoulda_macros/paperclip.rb create mode 100644 vendor/gems/paperclip/test/attachment_options_test.rb create mode 100644 vendor/gems/paperclip/test/attachment_test.rb create mode 100644 vendor/gems/paperclip/test/content_type_detector_test.rb create mode 100644 vendor/gems/paperclip/test/database.yml create mode 100644 vendor/gems/paperclip/test/fixtures/12k.png create mode 100644 vendor/gems/paperclip/test/fixtures/50x50.png create mode 100644 vendor/gems/paperclip/test/fixtures/5k.png create mode 100644 vendor/gems/paperclip/test/fixtures/animated create mode 100644 vendor/gems/paperclip/test/fixtures/animated.gif create mode 100644 vendor/gems/paperclip/test/fixtures/animated.unknown create mode 100644 vendor/gems/paperclip/test/fixtures/bad.png create mode 100644 vendor/gems/paperclip/test/fixtures/fog.yml create mode 100644 vendor/gems/paperclip/test/fixtures/s3.yml create mode 100644 vendor/gems/paperclip/test/fixtures/spaced file.png create mode 100644 vendor/gems/paperclip/test/fixtures/text.txt create mode 100644 vendor/gems/paperclip/test/fixtures/twopage.pdf create mode 100644 vendor/gems/paperclip/test/fixtures/uppercase.PNG create mode 100644 vendor/gems/paperclip/test/generator_test.rb create mode 100644 vendor/gems/paperclip/test/geometry_test.rb create mode 100644 vendor/gems/paperclip/test/helper.rb create mode 100644 vendor/gems/paperclip/test/integration_test.rb create mode 100644 vendor/gems/paperclip/test/interpolations_test.rb create mode 100644 vendor/gems/paperclip/test/io_adapters/abstract_adapter_test.rb create mode 100644 vendor/gems/paperclip/test/io_adapters/attachment_adapter_test.rb create mode 100644 vendor/gems/paperclip/test/io_adapters/file_adapter_test.rb create mode 100644 vendor/gems/paperclip/test/io_adapters/identity_adapter_test.rb create mode 100644 vendor/gems/paperclip/test/io_adapters/nil_adapter_test.rb create mode 100644 vendor/gems/paperclip/test/io_adapters/registry_test.rb create mode 100644 vendor/gems/paperclip/test/io_adapters/stringio_adapter_test.rb create mode 100644 vendor/gems/paperclip/test/io_adapters/uploaded_file_adapter_test.rb create mode 100644 vendor/gems/paperclip/test/io_adapters/uri_adapter_test.rb create mode 100644 vendor/gems/paperclip/test/matchers/have_attached_file_matcher_test.rb create mode 100644 vendor/gems/paperclip/test/matchers/validate_attachment_content_type_matcher_test.rb create mode 100644 vendor/gems/paperclip/test/matchers/validate_attachment_presence_matcher_test.rb create mode 100644 vendor/gems/paperclip/test/matchers/validate_attachment_size_matcher_test.rb create mode 100644 vendor/gems/paperclip/test/paperclip_missing_attachment_styles_test.rb create mode 100644 vendor/gems/paperclip/test/paperclip_test.rb create mode 100644 vendor/gems/paperclip/test/processor_test.rb create mode 100644 vendor/gems/paperclip/test/schema_test.rb create mode 100644 vendor/gems/paperclip/test/storage/filesystem_test.rb create mode 100644 vendor/gems/paperclip/test/storage/fog_test.rb create mode 100644 vendor/gems/paperclip/test/storage/s3_live_test.rb create mode 100644 vendor/gems/paperclip/test/storage/s3_test.rb create mode 100644 vendor/gems/paperclip/test/style_test.rb create mode 100644 vendor/gems/paperclip/test/support/mock_attachment.rb create mode 100644 vendor/gems/paperclip/test/support/mock_interpolator.rb create mode 100644 vendor/gems/paperclip/test/support/mock_model.rb create mode 100644 vendor/gems/paperclip/test/support/mock_url_generator_builder.rb create mode 100644 vendor/gems/paperclip/test/tempfile_factory_test.rb create mode 100644 vendor/gems/paperclip/test/thumbnail_test.rb create mode 100644 vendor/gems/paperclip/test/url_generator_test.rb create mode 100644 vendor/gems/paperclip/test/validators/attachment_content_type_validator_test.rb create mode 100644 vendor/gems/paperclip/test/validators/attachment_presence_validator_test.rb create mode 100644 vendor/gems/paperclip/test/validators/attachment_size_validator_test.rb create mode 100644 vendor/gems/paperclip/test/validators_test.rb diff --git a/vendor/gems/database_cleaner/.gitignore b/vendor/gems/database_cleaner/.gitignore new file mode 100644 index 0000000..09a18c2 --- /dev/null +++ b/vendor/gems/database_cleaner/.gitignore @@ -0,0 +1,11 @@ +*.sw? +.DS_Store +coverage +pkg +.bundle +bundled_gems/ +vendor/ +examples/db/*.db +examples/config/database.yml +db/config.yml +.vagrant diff --git a/vendor/gems/database_cleaner/.rbenv-version b/vendor/gems/database_cleaner/.rbenv-version new file mode 100644 index 0000000..220607f --- /dev/null +++ b/vendor/gems/database_cleaner/.rbenv-version @@ -0,0 +1 @@ +1.8.7-p249 diff --git a/vendor/gems/database_cleaner/.rspec b/vendor/gems/database_cleaner/.rspec new file mode 100644 index 0000000..660ad19 --- /dev/null +++ b/vendor/gems/database_cleaner/.rspec @@ -0,0 +1,4 @@ +--color +--format documentation +mtime +--backtrace diff --git a/vendor/gems/database_cleaner/.rvmrc b/vendor/gems/database_cleaner/.rvmrc new file mode 100644 index 0000000..6e85973 --- /dev/null +++ b/vendor/gems/database_cleaner/.rvmrc @@ -0,0 +1 @@ +rvm 1.8.7 diff --git a/vendor/gems/database_cleaner/.travis.yml b/vendor/gems/database_cleaner/.travis.yml new file mode 100644 index 0000000..b8fb38b --- /dev/null +++ b/vendor/gems/database_cleaner/.travis.yml @@ -0,0 +1,13 @@ +language: ruby +rvm: + - 1.8.7 +# TODO: fix ruby 1.9x build issues... +# - 1.9.3 +# TODO: make this work with the regular rake command +script: "bundle exec rake spec" +gemfile: + - Gemfile +before_script: + - mysql -e 'create database database_cleaner_test;' + - psql -c 'create database database_cleaner_test;' -U postgres + - cp db/sample.config.yml db/config.yml diff --git a/vendor/gems/database_cleaner/Gemfile b/vendor/gems/database_cleaner/Gemfile new file mode 100755 index 0000000..099ae23 --- /dev/null +++ b/vendor/gems/database_cleaner/Gemfile @@ -0,0 +1,43 @@ +source "http://rubygems.org" +# TODO: move these to the gemspec... + +group :development do + gem "rake" + gem "ruby-debug" + + gem "bundler" + gem "jeweler" + + gem "json_pure" + + #ORM's + gem "activerecord" + gem "datamapper", "1.0.0" + gem "dm-migrations", "1.0.0" + gem "dm-sqlite-adapter", "1.0.0" + gem "mongoid" + gem "tzinfo" + gem "mongo_ext" + gem "bson_ext" + gem "mongo_mapper" + gem "couch_potato" + gem "sequel", "~>3.21.0" + #gem "ibm_db" # I don't want to add this dependency, even as a dev one since it requires DB2 to be installed + gem 'mysql' + gem 'mysql2' + gem 'pg' + + gem 'guard-rspec' +end + +group :test do + gem "rspec-rails" + gem "rspactor" + gem "rcov" + gem "ZenTest" +end + +group :cucumber do + gem "cucumber" + gem 'sqlite3-ruby' +end diff --git a/vendor/gems/database_cleaner/Gemfile.lock b/vendor/gems/database_cleaner/Gemfile.lock new file mode 100644 index 0000000..bf3226e --- /dev/null +++ b/vendor/gems/database_cleaner/Gemfile.lock @@ -0,0 +1,231 @@ +GEM + remote: http://rubygems.org/ + specs: + ZenTest (4.8.1) + actionpack (3.2.6) + activemodel (= 3.2.6) + activesupport (= 3.2.6) + builder (~> 3.0.0) + erubis (~> 2.7.0) + journey (~> 1.0.1) + rack (~> 1.4.0) + rack-cache (~> 1.2) + rack-test (~> 0.6.1) + sprockets (~> 2.1.3) + activemodel (3.2.6) + activesupport (= 3.2.6) + builder (~> 3.0.0) + activerecord (3.2.6) + activemodel (= 3.2.6) + activesupport (= 3.2.6) + arel (~> 3.0.2) + tzinfo (~> 0.3.29) + activesupport (3.2.6) + i18n (~> 0.6) + multi_json (~> 1.0) + addressable (2.2.8) + arel (3.0.2) + bson (1.6.4) + bson_ext (1.6.4) + bson (~> 1.6.4) + builder (3.0.0) + columnize (0.3.6) + couch_potato (0.5.7) + activemodel + couchrest (>= 1.0.1) + json + couchrest (1.0.1) + json (>= 1.4.6) + mime-types (>= 1.15) + rest-client (>= 1.5.1) + cucumber (1.2.1) + builder (>= 2.1.2) + diff-lcs (>= 1.1.3) + gherkin (~> 2.11.0) + json (>= 1.4.6) + data_objects (0.10.8) + addressable (~> 2.1) + datamapper (1.0.0) + dm-aggregates (= 1.0.0) + dm-constraints (= 1.0.0) + dm-core (= 1.0.0) + dm-core (= 1.0.0) + dm-migrations (= 1.0.0) + dm-serializer (= 1.0.0) + dm-timestamps (= 1.0.0) + dm-transactions (= 1.0.0) + dm-types (= 1.0.0) + dm-validations (= 1.0.0) + diff-lcs (1.1.3) + dm-aggregates (1.0.0) + dm-core (~> 1.0.0) + dm-constraints (1.0.0) + dm-core (~> 1.0.0) + dm-migrations (~> 1.0.0) + dm-core (1.0.0) + addressable (~> 2.1) + extlib (~> 0.9.15) + dm-do-adapter (1.0.0) + data_objects (~> 0.10.1) + dm-core (~> 1.0.0) + dm-migrations (1.0.0) + dm-core (~> 1.0.0) + dm-serializer (1.0.0) + dm-core (~> 1.0.0) + fastercsv (~> 1.5.3) + json_pure (~> 1.4.3) + dm-sqlite-adapter (1.0.0) + dm-do-adapter (~> 1.0.0) + do_sqlite3 (~> 0.10.2) + dm-timestamps (1.0.0) + dm-core (~> 1.0.0) + dm-transactions (1.0.0) + dm-core (~> 1.0.0) + dm-types (1.0.0) + dm-core (~> 1.0.0) + fastercsv (~> 1.5.3) + json_pure (~> 1.4.3) + stringex (~> 1.1.0) + uuidtools (~> 2.1.1) + dm-validations (1.0.0) + dm-core (~> 1.0.0) + do_sqlite3 (0.10.8) + data_objects (= 0.10.8) + erubis (2.7.0) + extlib (0.9.15) + fastercsv (1.5.5) + ffi (1.1.4) + gherkin (2.11.1) + json (>= 1.4.6) + git (1.2.5) + guard (1.3.0) + listen (>= 0.4.2) + thor (>= 0.14.6) + guard-rspec (1.2.1) + guard (>= 1.1) + hike (1.2.1) + i18n (0.6.0) + jeweler (1.8.4) + bundler (~> 1.0) + git (>= 1.2.5) + rake + rdoc + journey (1.0.4) + json (1.7.3) + json_pure (1.4.6) + linecache (0.46) + rbx-require-relative (> 0.0.4) + listen (0.4.7) + rb-fchange (~> 0.0.5) + rb-fsevent (~> 0.9.1) + rb-inotify (~> 0.8.8) + mime-types (1.19) + mongo (1.6.4) + bson (~> 1.6.4) + mongo_ext (0.19.3) + mongo_mapper (0.11.1) + activemodel (~> 3.0) + activesupport (~> 3.0) + plucky (~> 0.4.0) + mongoid (3.0.1) + activemodel (~> 3.1) + moped (~> 1.1.1) + origin (~> 1.0.3) + tzinfo (~> 0.3.22) + moped (1.1.2) + multi_json (1.3.6) + mysql (2.8.1) + mysql2 (0.3.11) + origin (1.0.4) + pg (0.14.0) + plucky (0.4.4) + mongo (~> 1.5) + rack (1.4.1) + rack-cache (1.2) + rack (>= 0.4) + rack-ssl (1.3.2) + rack + rack-test (0.6.1) + rack (>= 1.0) + railties (3.2.6) + actionpack (= 3.2.6) + activesupport (= 3.2.6) + rack-ssl (~> 1.3.2) + rake (>= 0.8.7) + rdoc (~> 3.4) + thor (>= 0.14.6, < 2.0) + rake (0.9.2.2) + rb-fchange (0.0.5) + ffi + rb-fsevent (0.9.1) + rb-inotify (0.8.8) + ffi (>= 0.5.0) + rbx-require-relative (0.0.9) + rcov (1.0.0) + rdoc (3.12) + json (~> 1.4) + rest-client (1.6.7) + mime-types (>= 1.16) + rspactor (0.6.4) + rspec (2.11.0) + rspec-core (~> 2.11.0) + rspec-expectations (~> 2.11.0) + rspec-mocks (~> 2.11.0) + rspec-core (2.11.1) + rspec-expectations (2.11.1) + diff-lcs (~> 1.1.3) + rspec-mocks (2.11.1) + rspec-rails (2.11.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec (~> 2.11.0) + ruby-debug (0.10.4) + columnize (>= 0.1) + ruby-debug-base (~> 0.10.4.0) + ruby-debug-base (0.10.4) + linecache (>= 0.3) + sequel (3.21.0) + sprockets (2.1.3) + hike (~> 1.2) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + sqlite3 (1.3.6) + sqlite3-ruby (1.3.3) + sqlite3 (>= 1.3.3) + stringex (1.1.0) + thor (0.15.4) + tilt (1.3.3) + tzinfo (0.3.33) + uuidtools (2.1.3) + +PLATFORMS + ruby + +DEPENDENCIES + ZenTest + activerecord + bson_ext + bundler + couch_potato + cucumber + datamapper (= 1.0.0) + dm-migrations (= 1.0.0) + dm-sqlite-adapter (= 1.0.0) + guard-rspec + jeweler + json_pure + mongo_ext + mongo_mapper + mongoid + mysql + mysql2 + pg + rake + rcov + rspactor + rspec-rails + ruby-debug + sequel (~> 3.21.0) + sqlite3-ruby + tzinfo diff --git a/vendor/gems/database_cleaner/Guardfile b/vendor/gems/database_cleaner/Guardfile new file mode 100644 index 0000000..767287d --- /dev/null +++ b/vendor/gems/database_cleaner/Guardfile @@ -0,0 +1,9 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard 'rspec', :version => 2 do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } +end + diff --git a/vendor/gems/database_cleaner/History.txt b/vendor/gems/database_cleaner/History.txt new file mode 100644 index 0000000..6ac1544 --- /dev/null +++ b/vendor/gems/database_cleaner/History.txt @@ -0,0 +1,221 @@ +== 0.8.x (in git) + + * New options for AR :truncation for speed. See README for details. (Stanislaw Pankevich) + * view caching works with the schema_plus gem loaded + * ActiveRecord::ConnectionAdapters::AbstractAdapter#views was renamed to an internal name + * ActiveRecord truncation strategy caches the list of tables #130 (Petteri Räty) + +== 0.8.0 2012-06-02 + + * Faster truncation strategy for ActiveRecord with MySQL or PostgreSQL + * Upgrade to RSpec 2 + * Support for Mongoid 3/Moped (Andrew Bennett) + * Postgres Adapter no longer generates invalid SQL when no tables provided. (Michael-Keith Bernard) + +== 0.7.2 2012-03-21 + + * Proper Mysql2Adapter superclass fix. (Jonathan Viney) + * Sequel::Transaction works with latest Sequel. (David Barri) + * Documenation fixes/improvements. (David Barri, Ben Mabey, Kevin Moore) + +== 0.7.1 2012-01-15 + + === New Features + + * Support for Rails 3.2. (David Demaree) + +=== Bugfixes + + * Truncation resets the id count on SQLite. (Jordan Hollinger) + * AR delete strategy now disables referential integrity. (Ben Mabey) + * Fixes Postgres adapter for JRuby. (Dmytrii Nagirniak, Uģis Ozols) + * Documenation fixes. (Josh Rendek, Joshua Flanagan) + * Fixes bad error message when no database is specified for AR. (issue #72, Ben Mabey) + + +== 0.7.0 2011-11-12 + + === New Features + + * Sequel Support (Corin Langosch) + * Updates DataMapper strategies to work with DataMapper 1.1 (Xavier Shay and Anthony Williams) + * for AR and PSQL, truncate all tables with one command, improving performance due to avoiding cascades (Leonid Shevtsov) + +=== Bugfixes + + * Avoids trying to load the ':default' ActiveRecord config. #72 (Ben Mabey) + + +== 0.6.7 2011-04-21 + +=== Bugfixes + * Explicity require ERB. (Vít Ondruch) + * Cache DB connections, fixes referential integrity bug when using multiple DBs. (John Ferlito) + +== 0.6.6 2011-03-16 + +=== Bugfixes + * Don't modify the array passed in with the :except key. (Eric Wollesen) + * Fixes version checking for postgresql. (Greg Barnett) + +== 0.6.5 2011-03-08 + +=== Bugfixes + * When truncating in postgresql (>= 8.4) sequences are now reset. (Greg Barnett) + * Fixes the MongoDB truncation so non system collections starting with 'system' are not excluded for truncation. (Dmitry Naumov) + +== 0.6.4 2011-02-21 + +=== Bugfixes + * Avoids trying to drop views in Postgres. (Bernerd Schaefer) + +== 0.6.3 2011-02-09 + + === New Features + * Configurable logger to aid in debugging database cleaner. (Marty Haught) + +== 0.6.2 2011-02-04 + + === New Features + * Support IBM_DB Adapter for table truncation. This is for DB2 >= 9.7 (GH-39 Samer Abukhait) + + === Bugfixes + * Reversed GH-41 after larger community discussion. Mongo indexes are no longer dropped. (Ben Mabey) + * Truncation strategy works on SqlServer tables with FKs. (GH-33, Hugo Freire) + +== 0.6.1 2011-01-27 + + === New Features + * Default strategies for all ORM libs are defined. (GH-36, GH-38 Prem Sichanugrist) + * Add a NullStrategy. (GH-6 Ben Mabey) + + === Bugfixes + * Mongo colletion indexes are dropped for collections being removed. (GH-41 Ben Mabey) + * Exclude database views from tables_to_truncate, if the connection adapter + supports reading from the ANSI standard information_schema views. (GH-25 Samer Abukhait) + * ORM types can be specified in string format and not mysteriously blowup. (GH-26 Ben Mabey) + * Do not remove MongoDB reserved system collections. (GH-24 Ches Martin) + +== 0.6.0 2010-10-25 - The Multi-ORM/Connection Release + +This release has the often asked for functionality of being able to clean +multiple databases within the same project. This involves being able to +clean databases managed by the same ORM (i.e. different connections) and +also being able to clean databases managed by distinct ORMs. So, for +example you can now use DatabaseCleaner on a project that has ActiveRecord +and Mongoid to help ensure all DBs all in a clean state. Please see the +README for more information. The old API has been preserved so this release +is backwards compatible. + +This release is a result of Jon Rowe's hard work. Many thanks to Jon for all +of the hours and effort he put into making this feature request a reality. + + === New Features + * Ability to clean multiple database connections managed by the same ORM. (Jon Rowe) + * Ability to clean multiple DBs managed by different ORMs in same project. (Jon Rowe) + * Allows for the ActiveRecord config file (database.yml) to contain ERB and process it. (Fletcher Nichol) + * Mysql2Adapter support. (Kamal Fariz Mahyuddin and John Ferlito) + * Deletion strategy for ActiveRecord (Mikl Kurkov) + + === Bugfixes + * Updates the DataMapper truncation strategy to version 0.10.3. (Robert Rouse) + * Addresses Ruby 1.9 and 1.8 differences causing a bug in the AR PostgreSQLAdapter truncation strategy. (GH-14, James B. Byrne) + * Fixes syntax error that MySQL was throwing during DataMapper truncation. (Blake Gentry) + * Fixes truncation for PostgreSQL (Bodaniel Jeanes and Gabriel Sobrinho) + * Workaround for superclass mismatches for the ActiveRecord-jdbc-adapter (Toms Mikoss) + +== 0.5.2 + + === Bugfixes + * Removes extraneous puts call from configuration.rb. (Ben Mabey) + +== 0.5.1 - The Mongoid Release + +This release also attempts to fix AR for Rails 3 support. I have seen mixed reviews on this. Some people +claim the fixes allow for use in Rails3 while others have not had good luck with it. I plan on reworking +the way AR support is added so that it is more friendly with how Rails 3 uses autoload. + + === New features + * Clean and clean_with methods are now aliased to clean! and clean_with!. (Ben Mabey) + * Mongoid Support! (Sidney Burks) + +=== Bugfixes + * Check PostgreSQL version >= 8.2 before using TRUNCATE CASCADE (James B. Byrne) + * Correct superclass is used in ActiveRecord connection adapters. (johnathan, Aslak Hellesoy, Ben Mabey) + +== 0.5.0 2010-02-22 - The CouchPotato Release + + === New features + * Basic truncation support for CouchPotato / CouchDB. (Martin Rehfeld) + * SQLite3 on JRuby will fall back to delete if truncate doesn't work. (Darrin Holst) + * JDBC is used for ActiveRecord automaticaly when JRuby is detected. (Darrin Holst) + + === Bufixes + * MongoMapper truncation strategy now works with :only and :except options. (Ben Mabey) + +== 0.4.3 2010-01-17 + + === New features + * Truncation for ActiveRecord oracle_enhanced adapter. (Edgars Beigarts) + +== 0.4.2 2010-01-12 + + === Bufixes + * Datamapper truncation now uses 'select' instead of deprecated the 'query' method. (Steve Tooke) + +== 0.4.1 2010-01-07 + + === Bufixes + * Postgres tables with FKs now truncate (added TRUNCADE CASCADE) using Datamapper. (Ben Mabey) + +== 0.4.0 2009-12-23 (The MongoMapper Edition) + + === New features + * MongoMapper support for the truncation strategy. (Aubrey Holland) + +== 0.3.0 2009-12-20 + + === New features + * DataMapper 0.10.0 Compatible. (Martin Gamsjaeger) + === Bufixes + * Postgres tables with FKs now truncate (added TRUNCADE CASCADE). (Vika - yozhyk on github) + +== 0.2.3 2009-05-30 + + === New features + * Support for SQL Server truncation (Adam Meehan) + +== 0.2.2 2009-05-08 + === Bufixes + * Added proper gemspec description and summary. (Ben Mabey, thanks to Martin Gamsjaeger) + + === New features + +== 0.2.1 2009-05-08 + === Bufixes + * Removed extraneous TruncationBase class definition. (Ben Mabey) + +== 0.2.0 2009-05-08 - The Datamapper Release + + === New features + * DataMapper strategies (Martin Gamsjaeger) + * Transaction + * Truncation - working SQLite3, MySQL adapters. Experimental Postgres adapter (not tested). + +== 0.1.3 2009-04-30 + + === New features + * PostgresSQLAdapter for AR to support the truncation strategy. (Alberto Perdomo) + === Bufixes + * Added missing quotes around table names in truncation calls. (Michael MacDonald) + +== 0.1.2 2009-03-05 + === New features + * JDBC Adapter to enable AR truncation strategy to work. (Kamal Fariz Mahyuddin) + +== 0.1.1 2009-03-04 - Initial Release (Ben Mabey) + * Basic infrastructure + * Features, RSpec code examples + * ActiveRecord strategies + * Truncation - with MySQL, and SQLite3 adapters. + * Transaction - wrap your modifications and roll them back. diff --git a/vendor/gems/database_cleaner/LICENSE b/vendor/gems/database_cleaner/LICENSE new file mode 100644 index 0000000..96814ef --- /dev/null +++ b/vendor/gems/database_cleaner/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2009 Ben Mabey + +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. diff --git a/vendor/gems/database_cleaner/README.textile b/vendor/gems/database_cleaner/README.textile new file mode 100644 index 0000000..2aaf947 --- /dev/null +++ b/vendor/gems/database_cleaner/README.textile @@ -0,0 +1,245 @@ +h1. Database Cleaner + +Database Cleaner is a set of strategies for cleaning your database in Ruby. +The original use case was to ensure a clean state during tests. Each strategy +is a small amount of code but is code that is usually needed in any ruby app +that is testing with a database. + +ActiveRecord, DataMapper, Sequel, MongoMapper, Mongoid, and CouchPotato are supported. + +!https://secure.travis-ci.org/bmabey/database_cleaner.png(Build Status)!:http://travis-ci.org/bmabey/database_cleaner + +Here is an overview of the strategies supported for each library: + +|_. ORM |_. Truncation |_. Transaction |_. Deletion | +| ActiveRecord | Yes | **Yes** | Yes | +| DataMapper | Yes | **Yes** | No | +| CouchPotato | **Yes** | No | No | +| MongoMapper | **Yes** | No | No | +| Mongoid | **Yes** | No | No | +| Sequel | **Yes** | Yes | No | + +(Default strategy for each library is denoted in bold) + +Database Cleaner also includes a @null@ strategy (that does no cleaning at all) which can be used +with any ORM library. You can also explicitly use it by setting your strategy to @nil@. + +For support or to discuss development please use the "Google Group":http://groups.google.com/group/database_cleaner. + +h2(fastest). What strategy is fastest? + +For the SQL libraries the fastest option will be to use @:transaction@ as transactions are +simply rolled back. If you can use this strategy you should. However, if you wind up needing +to use multiple database connections in your tests (i.e. your tests run in a different proceess +than your application) then using this strategy becomes a bit more difficult. You can get around the +problem a number of ways. One common approach is to force all processes to use the same database +connection ("common ActiveRecord hack":http://blog.plataformatec.com.br/2011/12/three-tips-to-improve-the-performance-of-your-test-suite/) however this approach has been reported to result in +non-deterministic failures. Another approach is to have the transactions rolled back in the +application's process and relax the isolation level of the database (so the tests can read the +uncommited transactions). An easier, but slower, solution is to use the @:truncation@ or +@:deletion@ strategy. + +So what is fastest out of @:deletion@ and @:truncation@? Well, it depends on your table structure +and what percentage of tables you populate in an average test. The reasoning is out the the +scope of this README but here is a "good SO answer on this topic for Postgres":http://stackoverflow.com/questions/11419536/postgresql-truncation-speed/11423886#11423886. Some people report +much faster speeds with @:deletion@ while others say @:truncation@ is faster for them. The best approach therefore +is it try all options on your test suite and see what is faster. If you are using ActiveRecord then take a look +at the "additional options":#ar_truncation available for @:truncation@. + +h2. Dependencies + +Because database_cleaner supports multiple ORMs, it doesn't make sense to include all the dependencies +for each one in the gemspec. However, the DataMapper adapter does depend on dm-transactions. Therefore, +if you use DataMapper, you must include dm-transactions in your Gemfile/bundle/gemset manually. + +h2. How to use + +
+  require 'database_cleaner'
+  DatabaseCleaner.strategy = :truncation
+
+  # then, whenever you need to clean the DB
+  DatabaseCleaner.clean
+
+ +With the :truncation strategy you can also pass in options, for example: +
+  DatabaseCleaner.strategy = :truncation, {:only => %w[widgets dogs some_other_table]}
+
+ +
+  DatabaseCleaner.strategy = :truncation, {:except => %w[widgets]}
+
+ +(I should point out the truncation strategy will never truncate your schema_migrations table.) + +Some strategies require that you call DatabaseCleaner.start before calling clean +(for example the :transaction one needs to know to open up a transaction). So +you would have: + +
+  require 'database_cleaner'
+  DatabaseCleaner.strategy = :transaction
+
+  DatabaseCleaner.start # usually this is called in setup of a test
+  dirty_the_db
+  DatabaseCleaner.clean # cleanup of the test
+
+ +At times you may want to do a single clean with one strategy. For example, you may want +to start the process by truncating all the tables, but then use the faster transaction +strategy the remaining time. To accomplish this you can say: + +
+  require 'database_cleaner'
+  DatabaseCleaner.clean_with :truncation
+  DatabaseCleaner.strategy = :transaction
+  # then make the DatabaseCleaner.start and DatabaseCleaner.clean calls appropriately
+
+ +h3(#ar_truncation). Additional ActiveRecord options for Truncation + +The following options are available for ActiveRecord's @:truncation@ strategy _only_ for +MySQL and Postgres. + +* @:pre_count@ - When set to @true@ this will check each table for existing rows before +truncating it. This can speed up test suites when many of the tables to be truncated +are never populated. Defaults to @:false@. (Also, see the section on "What strategy is fastest?":#fastest) +* @:reset_ids@ - This only matters when @:pre_count@ is used, and it will make sure that a +tables auto-incrementing id is reset even if there are no rows in the table (e.g. records +were created in the test but also removed before DatabaseCleaner gets to it). Defaults to @true@. + + +h3. RSpec Example + +
+RSpec.configure do |config|
+
+  config.before(:suite) do
+    DatabaseCleaner.strategy = :transaction
+    DatabaseCleaner.clean_with(:truncation)
+  end
+
+  config.before(:each) do
+    DatabaseCleaner.start
+  end
+
+  config.after(:each) do
+    DatabaseCleaner.clean
+  end
+
+end
+
+ +h3. Minitest Example + +
+DatabaseCleaner.strategy = :transaction
+
+class MiniTest::Spec
+  before :each do
+    DatabaseCleaner.start
+  end
+
+  after :each do
+    DatabaseCleaner.clean
+  end
+end
+
+ +h3. Cucumber Example + +Add this to your features/support/env.rb file: + +
+begin
+  require 'database_cleaner'
+  require 'database_cleaner/cucumber'
+  DatabaseCleaner.strategy = :truncation
+rescue NameError
+  raise "You need to add database_cleaner to your Gemfile (in the :test group) if you wish to use it."
+end
+
+ +A good idea is to create the before and after hooks to use the DatabaseCleaner.start and DatabaseCleaner.clean methods. + +Inside features/support/hooks.rb: + +
+Before do
+  DatabaseCleaner.start
+end
+
+After do |scenario|
+  DatabaseCleaner.clean
+end
+
+ +This should cover the basics of tear down between scenarios and keeping your database clean. +For more examples see the section "Why?" + +h2. How to use with multiple ORM's + +Sometimes you need to use multiple ORMs in your application. You can use DatabaseCleaner to clean multiple ORMs, and multiple connections for those ORMs. + +
+  #How to specify particular orms
+  DatabaseCleaner[:active_record].strategy = :transaction
+  DatabaseCleaner[:mongo_mapper].strategy = :truncation
+
+  #How to specify particular connections
+  DatabaseCleaner[:active_record,{:connection => :two}]
+
+ +Usage beyond that remains the same with DatabaseCleaner.start calling any setup on the different configured connections, and DatabaseCleaner.clean executing afterwards. + +Configuration options + + +|_. ORM |_. How to access |_. Notes | +| Active Record | DatabaseCleaner[:active_record] | Connection specified as :symbol keys, loaded from config/database.yml | +| Data Mapper | DatabaseCleaner[:data_mapper] | Connection specified as :symbol keys, loaded via Datamapper repositories | +| Mongo Mapper | DatabaseCleaner[:mongo_mapper] | Multiple connections not yet supported | +| Mongoid | DatabaseCleaner[:mongoid] | Multiple connections not yet supported | +| Couch Potato | DatabaseCleaner[:couch_potato] | Multiple connections not yet supported | +| Sequel | DatabaseCleaner[:sequel] | ? | + +h2. Why? + +One of my motivations for writing this library was to have an easy way to turn on what Rails calls "transactional_fixtures" +in my non-rails ActiveRecord projects. After copying and pasting code to do this several times I decided to package it up +as a gem and same everyone a bit of time. + +h2. Common Errors + +h4. DatabaseCleaner is trying to use the wrong ORM + +DatabaseCleaner has an autodetect mechanism where if you do not explicitly define your ORM it will use the first ORM it can detect that is loaded. Since ActiveRecord is the most common ORM used that is the first one checked for. Sometimes other libraries (e.g. ActiveAdmin) will load other ORMs (e.g. ActiveRecord) even though you are using a different ORM. This will result in DatabaseCleaner trying to use the wrong ORM (e.g. ActiveRecord) unless you explicitly define your ORM like so: + +
+  # How to setup your ORM explicitly
+  DatabaseCleaner[:mongoid].strategy = :truncation
+
+ +h4. STDERR is being flooded when using Postgres + +If you are using Postgres and have foreign key constraints, the truncation strategy will cause a lot of extra noise to appear on STDERR (in +the form of "NOTICE truncate cascades" messages). To silence these warnings set the following log level in your postgresql.conf file: + +
+  client_min_messages = warning
+
+ + +h2. Debugging + +In rare cases DatabaseCleaner will encounter errors that it will log. By default it uses STDOUT set to the ERROR level but you can configure this to use whatever Logger you desire. Here's an example of using the Rails.logger in env.rb: + +
+  DatabaseCleaner.logger = Rails.logger
+
+ + +h2. COPYRIGHT + +Copyright (c) 2009 Ben Mabey. See LICENSE for details. diff --git a/vendor/gems/database_cleaner/Rakefile b/vendor/gems/database_cleaner/Rakefile new file mode 100644 index 0000000..efd0f06 --- /dev/null +++ b/vendor/gems/database_cleaner/Rakefile @@ -0,0 +1,55 @@ +require "rubygems" +require "bundler" +Bundler.setup + +require 'rake' + +begin + require 'jeweler' + Jeweler::Tasks.new do |s| + s.name = "database_cleaner" + s.summary = %Q{Strategies for cleaning databases. Can be used to ensure a clean state for testing.} + s.email = "ben@benmabey.com" + s.homepage = "http://github.com/bmabey/database_cleaner" + s.description = "Strategies for cleaning databases. Can be used to ensure a clean state for testing." + s.files = FileList["[A-Z]*.*", "{examples,lib,features,spec}/**/*", "Rakefile", "cucumber.yml"] + s.authors = ["Ben Mabey"] + end +rescue LoadError + puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com" +end + +require 'rspec/core' +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) do |spec| + spec.pattern = FileList['spec/**/*_spec.rb'] +end + +RSpec::Core::RakeTask.new(:rcov) do |spec| + spec.pattern = 'spec/**/*_spec.rb' + spec.rcov = true +end + +begin + require 'cucumber/rake/task' + Cucumber::Rake::Task.new(:features) +rescue LoadError + puts "Cucumber is not available. In order to run features, you must: sudo gem install cucumber" +end + +task :default => [:spec, :features] + + +desc "Cleans the project of any tmp file that should not be included in the gemspec." +task :clean do + ["examples/config/database.yml", "examples/db/activerecord_one.db", "examples/db/activerecord_two.db", "examples/db/datamapper_default.db", + "examples/db/datamapper_one.db", "examples/db/datamapper_two.db"].each do |f| + FileUtils.rm_f(f) + end + %w[*.sqlite3 *.log #* *.swp *.swo].each do |pattern| + `find . -name "#{pattern}" -delete` + end +end + +desc "Cleans the dir and builds the gem" +task :prep => [:clean, :gemspec, :build] diff --git a/vendor/gems/database_cleaner/TODO b/vendor/gems/database_cleaner/TODO new file mode 100644 index 0000000..3116452 --- /dev/null +++ b/vendor/gems/database_cleaner/TODO @@ -0,0 +1,3 @@ +Could be more Datamapper +MongoMapper multiple db support +CouchDB multiple db support \ No newline at end of file diff --git a/vendor/gems/database_cleaner/VERSION.yml b/vendor/gems/database_cleaner/VERSION.yml new file mode 100644 index 0000000..5cb9009 --- /dev/null +++ b/vendor/gems/database_cleaner/VERSION.yml @@ -0,0 +1,5 @@ +--- +:major: 0 +:build: +:minor: 8 +:patch: 0 diff --git a/vendor/gems/database_cleaner/cucumber.yml b/vendor/gems/database_cleaner/cucumber.yml new file mode 100644 index 0000000..c1869b5 --- /dev/null +++ b/vendor/gems/database_cleaner/cucumber.yml @@ -0,0 +1 @@ +default: features diff --git a/vendor/gems/database_cleaner/database_cleaner.gemspec b/vendor/gems/database_cleaner/database_cleaner.gemspec new file mode 100644 index 0000000..928a090 --- /dev/null +++ b/vendor/gems/database_cleaner/database_cleaner.gemspec @@ -0,0 +1,149 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = %q{database_cleaner} + s.version = "0.8.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Ben Mabey"] + s.date = %q{2012-06-02} + s.description = %q{Strategies for cleaning databases. Can be used to ensure a clean state for testing.} + s.email = %q{ben@benmabey.com} + s.extra_rdoc_files = [ + "LICENSE", + "README.textile", + "TODO" + ] + s.files = [ + "Gemfile.lock", + "History.txt", + "README.textile", + "Rakefile", + "VERSION.yml", + "cucumber.yml", + "examples/Gemfile", + "examples/Gemfile.lock", + "examples/config/database.yml.example", + "examples/db/sqlite_databases_go_here", + "examples/features/example.feature", + "examples/features/example_multiple_db.feature", + "examples/features/example_multiple_orm.feature", + "examples/features/step_definitions/activerecord_steps.rb", + "examples/features/step_definitions/couchpotato_steps.rb", + "examples/features/step_definitions/datamapper_steps.rb", + "examples/features/step_definitions/mongoid_steps.rb", + "examples/features/step_definitions/mongomapper_steps.rb", + "examples/features/step_definitions/translation_steps.rb", + "examples/features/support/env.rb", + "examples/lib/activerecord_models.rb", + "examples/lib/couchpotato_models.rb", + "examples/lib/datamapper_models.rb", + "examples/lib/mongoid_models.rb", + "examples/lib/mongomapper_models.rb", + "features/cleaning.feature", + "features/cleaning_default_strategy.feature", + "features/cleaning_multiple_dbs.feature", + "features/cleaning_multiple_orms.feature", + "features/step_definitions/database_cleaner_steps.rb", + "features/support/env.rb", + "features/support/feature_runner.rb", + "lib/database_cleaner.rb", + "lib/database_cleaner/active_record/base.rb", + "lib/database_cleaner/active_record/deletion.rb", + "lib/database_cleaner/active_record/transaction.rb", + "lib/database_cleaner/active_record/truncation.rb", + "lib/database_cleaner/base.rb", + "lib/database_cleaner/configuration.rb", + "lib/database_cleaner/couch_potato/base.rb", + "lib/database_cleaner/couch_potato/truncation.rb", + "lib/database_cleaner/cucumber.rb", + "lib/database_cleaner/data_mapper/base.rb", + "lib/database_cleaner/data_mapper/transaction.rb", + "lib/database_cleaner/data_mapper/truncation.rb", + "lib/database_cleaner/generic/base.rb", + "lib/database_cleaner/generic/transaction.rb", + "lib/database_cleaner/generic/truncation.rb", + "lib/database_cleaner/mongo/truncation.rb", + "lib/database_cleaner/mongo_mapper/base.rb", + "lib/database_cleaner/mongo_mapper/truncation.rb", + "lib/database_cleaner/mongoid/base.rb", + "lib/database_cleaner/mongoid/truncation.rb", + "lib/database_cleaner/moped/truncation.rb", + "lib/database_cleaner/null_strategy.rb", + "lib/database_cleaner/sequel/base.rb", + "lib/database_cleaner/sequel/transaction.rb", + "lib/database_cleaner/sequel/truncation.rb", + "spec/database_cleaner/active_record/base_spec.rb", + "spec/database_cleaner/active_record/transaction_spec.rb", + "spec/database_cleaner/active_record/truncation_spec.rb", + "spec/database_cleaner/base_spec.rb", + "spec/database_cleaner/configuration_spec.rb", + "spec/database_cleaner/couch_potato/truncation_spec.rb", + "spec/database_cleaner/data_mapper/base_spec.rb", + "spec/database_cleaner/data_mapper/transaction_spec.rb", + "spec/database_cleaner/data_mapper/truncation_spec.rb", + "spec/database_cleaner/generic/base_spec.rb", + "spec/database_cleaner/generic/truncation_spec.rb", + "spec/database_cleaner/mongo_mapper/base_spec.rb", + "spec/database_cleaner/mongo_mapper/mongo_examples.rb", + "spec/database_cleaner/mongo_mapper/truncation_spec.rb", + "spec/database_cleaner/sequel/base_spec.rb", + "spec/database_cleaner/sequel/transaction_spec.rb", + "spec/database_cleaner/sequel/truncation_spec.rb", + "spec/database_cleaner/shared_strategy.rb", + "spec/rcov.opts", + "spec/spec.opts", + "spec/spec_helper.rb" + ] + s.homepage = %q{http://github.com/bmabey/database_cleaner} + s.rdoc_options = ["--charset=UTF-8"] + s.require_paths = ["lib"] + s.rubygems_version = %q{1.6.2} + s.summary = %q{Strategies for cleaning databases. Can be used to ensure a clean state for testing.} + s.test_files = [ + "spec/database_cleaner/active_record/base_spec.rb", + "spec/database_cleaner/active_record/transaction_spec.rb", + "spec/database_cleaner/active_record/truncation_spec.rb", + "spec/database_cleaner/base_spec.rb", + "spec/database_cleaner/configuration_spec.rb", + "spec/database_cleaner/couch_potato/truncation_spec.rb", + "spec/database_cleaner/data_mapper/base_spec.rb", + "spec/database_cleaner/data_mapper/transaction_spec.rb", + "spec/database_cleaner/data_mapper/truncation_spec.rb", + "spec/database_cleaner/generic/base_spec.rb", + "spec/database_cleaner/generic/truncation_spec.rb", + "spec/database_cleaner/mongo_mapper/base_spec.rb", + "spec/database_cleaner/mongo_mapper/mongo_examples.rb", + "spec/database_cleaner/mongo_mapper/truncation_spec.rb", + "spec/database_cleaner/sequel/base_spec.rb", + "spec/database_cleaner/sequel/transaction_spec.rb", + "spec/database_cleaner/sequel/truncation_spec.rb", + "spec/database_cleaner/shared_strategy.rb", + "spec/spec_helper.rb", + "examples/features/step_definitions/activerecord_steps.rb", + "examples/features/step_definitions/couchpotato_steps.rb", + "examples/features/step_definitions/datamapper_steps.rb", + "examples/features/step_definitions/mongoid_steps.rb", + "examples/features/step_definitions/mongomapper_steps.rb", + "examples/features/step_definitions/translation_steps.rb", + "examples/features/support/env.rb", + "examples/lib/activerecord_models.rb", + "examples/lib/couchpotato_models.rb", + "examples/lib/datamapper_models.rb", + "examples/lib/mongoid_models.rb", + "examples/lib/mongomapper_models.rb" + ] + + if s.respond_to? :specification_version then + s.specification_version = 3 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + else + end + else + end +end + diff --git a/vendor/gems/database_cleaner/db/sample.config.yml b/vendor/gems/database_cleaner/db/sample.config.yml new file mode 100644 index 0000000..142ea80 --- /dev/null +++ b/vendor/gems/database_cleaner/db/sample.config.yml @@ -0,0 +1,26 @@ +mysql: + adapter: mysql + database: database_cleaner_test + username: root + password: + host: 127.0.0.1 + port: 3306 + encoding: utf8 + +mysql2: + adapter: mysql2 + database: database_cleaner_test + username: root + password: + host: 127.0.0.1 + port: 3306 + encoding: utf8 + +postgres: + adapter: postgresql + database: database_cleaner_test + username: postgres + password: + host: 127.0.0.1 + encoding: unicode + template: template0 diff --git a/vendor/gems/database_cleaner/examples/Gemfile b/vendor/gems/database_cleaner/examples/Gemfile new file mode 120000 index 0000000..26cb2ad --- /dev/null +++ b/vendor/gems/database_cleaner/examples/Gemfile @@ -0,0 +1 @@ +../Gemfile \ No newline at end of file diff --git a/vendor/gems/database_cleaner/examples/Gemfile.lock b/vendor/gems/database_cleaner/examples/Gemfile.lock new file mode 120000 index 0000000..412e45f --- /dev/null +++ b/vendor/gems/database_cleaner/examples/Gemfile.lock @@ -0,0 +1 @@ +../Gemfile.lock \ No newline at end of file diff --git a/vendor/gems/database_cleaner/examples/config/database.yml.example b/vendor/gems/database_cleaner/examples/config/database.yml.example new file mode 100644 index 0000000..64f2dec --- /dev/null +++ b/vendor/gems/database_cleaner/examples/config/database.yml.example @@ -0,0 +1,8 @@ +#This is an example of what database.yml *should* look like (when I wrote it) +#The real database.yml is generated automatically by the active record model lib (so it can be correct) +two: + adapter: sqlite3 + database: /path/to/examples/features/support/../../db/activerecord_two.db +one: + adapter: sqlite3 + database: /path/to/examples/features/support/../../db/activerecord_one.db diff --git a/vendor/gems/database_cleaner/examples/db/sqlite_databases_go_here b/vendor/gems/database_cleaner/examples/db/sqlite_databases_go_here new file mode 100644 index 0000000..e69de29 diff --git a/vendor/gems/database_cleaner/examples/features/example.feature b/vendor/gems/database_cleaner/examples/features/example.feature new file mode 100644 index 0000000..7559bd7 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/features/example.feature @@ -0,0 +1,11 @@ +Feature: example + In order to test DataBase Cleaner + Here are some scenarios that rely on the DB being clean! + + Scenario: dirty the db + When I create a widget + Then I should see 1 widget + + Scenario: assume a clean db + When I create a widget + Then I should see 1 widget diff --git a/vendor/gems/database_cleaner/examples/features/example_multiple_db.feature b/vendor/gems/database_cleaner/examples/features/example_multiple_db.feature new file mode 100644 index 0000000..d9bbb66 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/features/example_multiple_db.feature @@ -0,0 +1,23 @@ +Feature: example + In order to test DataBase Cleaner + Here are some scenarios that rely on the DB being clean! + + # Background: + # Given I have setup DatabaseCleaner to clean multiple databases + # + Scenario: dirty the db + When I create a widget in one db + And I create a widget in another db + Then I should see 1 widget in one db + And I should see 1 widget in another db + + Scenario: assume a clean db + When I create a widget in one db + Then I should see 1 widget in one db + And I should see 0 widget in another db + + Scenario: assume a clean db + When I create a widget in another db + Then I should see 0 widget in one db + And I should see 1 widget in another db + diff --git a/vendor/gems/database_cleaner/examples/features/example_multiple_orm.feature b/vendor/gems/database_cleaner/examples/features/example_multiple_orm.feature new file mode 100644 index 0000000..c800633 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/features/example_multiple_orm.feature @@ -0,0 +1,22 @@ +Feature: example + In order to test DataBase Cleaner + Here are some scenarios that rely on the DB being clean! + + # Background: + # Given I have setup DatabaseCleaner to clean multiple orms + + Scenario: dirty the db + When I create a widget in one orm + And I create a widget in another orm + Then I should see 1 widget in one orm + And I should see 1 widget in another orm + + Scenario: assume a clean db + When I create a widget in one orm + Then I should see 1 widget in one orm + And I should see 0 widget in another orm + + Scenario: assume a clean db + When I create a widget in another orm + Then I should see 0 widget in one orm + And I should see 1 widget in another orm diff --git a/vendor/gems/database_cleaner/examples/features/step_definitions/activerecord_steps.rb b/vendor/gems/database_cleaner/examples/features/step_definitions/activerecord_steps.rb new file mode 100644 index 0000000..a242987 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/features/step_definitions/activerecord_steps.rb @@ -0,0 +1,31 @@ +Given /^I have setup database cleaner to clean multiple databases using activerecord$/ do + #DatabaseCleaner + # require "#{File.dirname(__FILE__)}/../../../lib/datamapper_models" + # + # DatabaseCleaner[:datamapper, {:connection => :one} ].strategy = :truncation + # DatabaseCleaner[:datamapper, {:connection => :two} ].strategy = :truncation +end + +When /^I create a widget using activerecord$/ do + ActiveRecordWidget.create! +end + +Then /^I should see ([\d]+) widget using activerecord$/ do |widget_count| + ActiveRecordWidget.count.should == widget_count.to_i +end + +When /^I create a widget in one db using activerecord$/ do + ActiveRecordWidgetUsingDatabaseOne.create! +end + +When /^I create a widget in another db using activerecord$/ do + ActiveRecordWidgetUsingDatabaseTwo.create! +end + +Then /^I should see ([\d]+) widget in one db using activerecord$/ do |widget_count| + ActiveRecordWidgetUsingDatabaseOne.count.should == widget_count.to_i +end + +Then /^I should see ([\d]+) widget in another db using activerecord$/ do |widget_count| + ActiveRecordWidgetUsingDatabaseTwo.count.should == widget_count.to_i +end diff --git a/vendor/gems/database_cleaner/examples/features/step_definitions/couchpotato_steps.rb b/vendor/gems/database_cleaner/examples/features/step_definitions/couchpotato_steps.rb new file mode 100644 index 0000000..55f322f --- /dev/null +++ b/vendor/gems/database_cleaner/examples/features/step_definitions/couchpotato_steps.rb @@ -0,0 +1,31 @@ +Given /^I have setup database cleaner to clean multiple databases using couchpotato$/ do + #DatabaseCleaner + # require "#{File.dirname(__FILE__)}/../../../lib/couchpotato_models" + # + # DatabaseCleaner[:couchpotato, {:connection => :one} ].strategy = :truncation + # DatabaseCleaner[:couchpotato, {:connection => :two} ].strategy = :truncation +end + +When /^I create a widget using couchpotato$/ do + CouchPotatoWidget.create! +end + +Then /^I should see ([\d]+) widget using couchpotato$/ do |widget_count| + CouchPotatoWidget.count.should == widget_count.to_i +end + +When /^I create a widget in one db using couchpotato$/ do + CouchPotatoWidgetUsingDatabaseOne.create! +end + +When /^I create a widget in another db using couchpotato$/ do + CouchPotatoWidgetUsingDatabaseTwo.create! +end + +Then /^I should see ([\d]+) widget in one db using couchpotato$/ do |widget_count| + CouchPotatoWidgetUsingDatabaseOne.count.should == widget_count.to_i +end + +Then /^I should see ([\d]+) widget in another db using couchpotato$/ do |widget_count| + CouchPotatoWidgetUsingDatabaseTwo.count.should == widget_count.to_i +end diff --git a/vendor/gems/database_cleaner/examples/features/step_definitions/datamapper_steps.rb b/vendor/gems/database_cleaner/examples/features/step_definitions/datamapper_steps.rb new file mode 100644 index 0000000..9e110f3 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/features/step_definitions/datamapper_steps.rb @@ -0,0 +1,37 @@ +Given /^I have setup database cleaner to clean multiple databases using datamapper$/ do + #DatabaseCleaner + # require "#{File.dirname(__FILE__)}/../../../lib/datamapper_models" + # + # DatabaseCleaner[:datamapper, {:connection => :one} ].strategy = :truncation + # DatabaseCleaner[:datamapper, {:connection => :two} ].strategy = :truncation +end + +When /^I create a widget using datamapper$/ do + DataMapperWidget.create! +end + +Then /^I should see ([\d]+) widget using datamapper$/ do |widget_count| + DataMapperWidget.count.should == widget_count.to_i +end + +When /^I create a widget in one db using datamapper$/ do + begin + DataMapperWidgetUsingDatabaseOne.create! + rescue StandardError => e + BREAK = e.backtrace + debugger + DataMapperWidgetUsingDatabaseOne.create! + end +end + +When /^I create a widget in another db using datamapper$/ do + DataMapperWidgetUsingDatabaseTwo.create! +end + +Then /^I should see ([\d]+) widget in one db using datamapper$/ do |widget_count| + DataMapperWidgetUsingDatabaseOne.count.should == widget_count.to_i +end + +Then /^I should see ([\d]+) widget in another db using datamapper$/ do |widget_count| + DataMapperWidgetUsingDatabaseTwo.count.should == widget_count.to_i +end diff --git a/vendor/gems/database_cleaner/examples/features/step_definitions/mongoid_steps.rb b/vendor/gems/database_cleaner/examples/features/step_definitions/mongoid_steps.rb new file mode 100644 index 0000000..3cb4c4a --- /dev/null +++ b/vendor/gems/database_cleaner/examples/features/step_definitions/mongoid_steps.rb @@ -0,0 +1,23 @@ +When /^I create a widget using mongoid$/ do + MongoidWidget.create!( :id => rand(1000)+1000) +end + +Then /^I should see ([\d]+) widget using mongoid$/ do |widget_count| + MongoidWidget.count.should == widget_count.to_i +end + +When /^I create a widget in one db using mongoid$/ do + MongoidWidgetUsingDatabaseOne.create! +end + +When /^I create a widget in another db using mongoid$/ do + MongoidWidgetUsingDatabaseTwo.create! +end + +Then /^I should see ([\d]+) widget in one db using mongoid$/ do |widget_count| + MongoidWidgetUsingDatabaseOne.count.should == widget_count.to_i +end + +Then /^I should see ([\d]+) widget in another db using mongoid$/ do |widget_count| + MongoidWidgetUsingDatabaseTwo.count.should == widget_count.to_i +end diff --git a/vendor/gems/database_cleaner/examples/features/step_definitions/mongomapper_steps.rb b/vendor/gems/database_cleaner/examples/features/step_definitions/mongomapper_steps.rb new file mode 100644 index 0000000..12d82cd --- /dev/null +++ b/vendor/gems/database_cleaner/examples/features/step_definitions/mongomapper_steps.rb @@ -0,0 +1,31 @@ +Given /^I have setup database cleaner to clean multiple databases using mongomapper$/ do + #DatabaseCleaner + # require "#{File.dirname(__FILE__)}/../../../lib/datamapper_models" + # + # DatabaseCleaner[:datamapper, {:connection => :one} ].strategy = :truncation + # DatabaseCleaner[:datamapper, {:connection => :two} ].strategy = :truncation +end + +When /^I create a widget using mongomapper$/ do + MongoMapperWidget.create! +end + +Then /^I should see ([\d]+) widget using mongomapper$/ do |widget_count| + MongoMapperWidget.count.should == widget_count.to_i +end + +When /^I create a widget in one db using mongomapper$/ do + MongoMapperWidgetUsingDatabaseOne.create! +end + +When /^I create a widget in another db using mongomapper$/ do + MongoMapperWidgetUsingDatabaseTwo.create! +end + +Then /^I should see ([\d]+) widget in one db using mongomapper$/ do |widget_count| + MongoMapperWidgetUsingDatabaseOne.count.should == widget_count.to_i +end + +Then /^I should see ([\d]+) widget in another db using mongomapper$/ do |widget_count| + MongoMapperWidgetUsingDatabaseTwo.count.should == widget_count.to_i +end diff --git a/vendor/gems/database_cleaner/examples/features/step_definitions/translation_steps.rb b/vendor/gems/database_cleaner/examples/features/step_definitions/translation_steps.rb new file mode 100644 index 0000000..325a649 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/features/step_definitions/translation_steps.rb @@ -0,0 +1,55 @@ +When /^I create a widget$/ do + step "I create a widget using #{ENV['ORM'].downcase}" +end + +Then /^I should see 1 widget$/ do + step "I should see 1 widget using #{ENV['ORM'].downcase}" +end + +When /^I create a widget in one orm$/ do + step "I create a widget using #{ENV['ORM'].downcase}" +end + +When /^I create a widget in another orm$/ do + step "I create a widget using #{ENV['ANOTHER_ORM'].downcase}" +end + +Then /^I should see 1 widget in one orm$/ do + step "I should see 1 widget using #{ENV['ORM'].downcase}" +end + +Then /^I should see 1 widget in another orm$/ do + step "I should see 1 widget using #{ENV['ANOTHER_ORM'].downcase}" +end + +Then /^I should see 0 widget in another orm$/ do + step "I should see 0 widget using #{ENV['ANOTHER_ORM'].downcase}" +end + +Then /^I should see 0 widget in one orm$/ do + step "I should see 0 widget using #{ENV['ORM'].downcase}" +end + +When /^I create a widget in one db$/ do + step "I create a widget in one db using #{ENV['ORM'].downcase}" +end + +When /^I create a widget in another db$/ do + step "I create a widget in another db using #{ENV['ORM'].downcase}" +end + +Then /^I should see 1 widget in one db$/ do + step "I should see 1 widget in one db using #{ENV['ORM'].downcase}" +end + +Then /^I should see 1 widget in another db$/ do + step "I should see 1 widget in another db using #{ENV['ORM'].downcase}" +end + +Then /^I should see 0 widget in another db$/ do + step "I should see 0 widget in another db using #{ENV['ORM'].downcase}" +end + +Then /^I should see 0 widget in one db$/ do + step "I should see 0 widget in one db using #{ENV['ORM'].downcase}" +end diff --git a/vendor/gems/database_cleaner/examples/features/support/env.rb b/vendor/gems/database_cleaner/examples/features/support/env.rb new file mode 100644 index 0000000..2edcef9 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/features/support/env.rb @@ -0,0 +1,62 @@ +#Hilarious as it seems, this is necessary so bundle exec cucumber works for mongoid cukeage (I'm assuming mongomapper is automatically present because its a git repo) +Object.send(:remove_const, 'MongoMapper') if defined?(::MongoMapper) + +require 'rubygems' +require 'bundler' + +Bundler.setup +require 'rspec/expectations' +require 'ruby-debug' + +DB_DIR = "#{File.dirname(__FILE__)}/../../db" + +orm = ENV['ORM'] +another_orm = ENV['ANOTHER_ORM'] +strategy = ENV['STRATEGY'] +multiple_db = ENV['MULTIPLE_DBS'] + + +if orm && strategy + $:.unshift(File.dirname(__FILE__) + '/../../../lib') + require 'database_cleaner' + require 'database_cleaner/cucumber' + + begin + require "#{File.dirname(__FILE__)}/../../lib/#{orm.downcase}_models" + rescue LoadError => e + raise "You don't have the #{orm} ORM installed" + end + + if another_orm + begin + require "#{File.dirname(__FILE__)}/../../lib/#{another_orm.downcase}_models" + rescue LoadError => e + raise "You don't have the #{another_orm} ORM installed" + end + end + + + + + if multiple_db + DatabaseCleaner.app_root = "#{File.dirname(__FILE__)}/../.." + orm_sym = orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym + + if orm_sym == :mongo_mapper + DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_one'} ].strategy = strategy.to_sym + DatabaseCleaner[ orm_sym, {:connection => 'database_cleaner_test_two'} ].strategy = strategy.to_sym + else + DatabaseCleaner[ orm_sym, {:connection => :one} ].strategy = strategy.to_sym + DatabaseCleaner[ orm_sym, {:connection => :two} ].strategy = strategy.to_sym + end + + elsif another_orm + DatabaseCleaner[ orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym ].strategy = strategy.to_sym + DatabaseCleaner[ another_orm.gsub(/(.)([A-Z]+)/,'\1_\2').downcase.to_sym ].strategy = strategy.to_sym + else + DatabaseCleaner.strategy = strategy.to_sym unless strategy == "default" + end + +else + raise "Run 'ORM=ActiveRecord|DataMapper|MongoMapper|CouchPotato [ANOTHER_ORM=...] [MULTIPLE_DBS=true] STRATEGY=transaction|truncation|default cucumber examples/features'" +end diff --git a/vendor/gems/database_cleaner/examples/lib/activerecord_models.rb b/vendor/gems/database_cleaner/examples/lib/activerecord_models.rb new file mode 100644 index 0000000..fea4b4d --- /dev/null +++ b/vendor/gems/database_cleaner/examples/lib/activerecord_models.rb @@ -0,0 +1,41 @@ +require 'active_record' +databases_config = { + "one" => {"adapter" => "#{"jdbc" if defined?(JRUBY_VERSION)}sqlite3", "database" => "#{DB_DIR}/activerecord_one.db"}, + "two" => {"adapter" => "#{"jdbc" if defined?(JRUBY_VERSION)}sqlite3", "database" => "#{DB_DIR}/activerecord_two.db"} +} + +File.open("#{File.dirname(__FILE__)}/../config/database.yml", 'w') do |file| + file.write(YAML.dump(databases_config)) +end + +["two","one"].each do |db| + ActiveRecord::Base.establish_connection(databases_config[db]) + ActiveRecord::Base.connection.execute('DROP TABLE IF EXISTS "active_record_widgets"') + ActiveRecord::Base.connection.execute('DROP TABLE IF EXISTS "active_record_widget_using_database_ones"') + ActiveRecord::Base.connection.execute('DROP TABLE IF EXISTS "active_record_widget_using_database_twos"') + + ActiveRecord::Schema.define(:version => 1) do + create_table :active_record_widgets do |t| + t.string :name + end + + create_table :active_record_widget_using_database_ones do |t| + t.string :name + end + + create_table :active_record_widget_using_database_twos do |t| + t.string :name + end + end +end + +class ActiveRecordWidget < ActiveRecord::Base +end + +class ActiveRecordWidgetUsingDatabaseOne < ActiveRecord::Base + establish_connection(:adapter => "#{"jdbc" if defined?(JRUBY_VERSION)}sqlite3", :database => "#{DB_DIR}/activerecord_one.db") +end + +class ActiveRecordWidgetUsingDatabaseTwo < ActiveRecord::Base + establish_connection(:adapter => "#{"jdbc" if defined?(JRUBY_VERSION)}sqlite3", :database => "#{DB_DIR}/activerecord_two.db") +end diff --git a/vendor/gems/database_cleaner/examples/lib/couchpotato_models.rb b/vendor/gems/database_cleaner/examples/lib/couchpotato_models.rb new file mode 100644 index 0000000..03b3cc3 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/lib/couchpotato_models.rb @@ -0,0 +1,61 @@ +require 'couch_potato' +require 'json/pure' unless defined? ::JSON +::CouchPotato::Config.database_name = 'couch_potato_test' + +class CouchPotatoWidget + include CouchPotato::Persistence + + property :name + view :by_name, :key => :name + + + # mimic the AR interface used in example_steps + + def self.create!(attrs = {}) + CouchPotato.database.save(self.new) + end + + def self.count + CouchPotato.database.view(self.by_name).size + end +end + +class CouchPotatoWidgetUsingDatabaseOne + include CouchPotato::Persistence + + database_name = 'couch_potato_test_one' + + property :name + view :by_name, :key => :name + + + # mimic the AR interface used in example_steps + + def self.create!(attrs = {}) + CouchPotato.database.save(self.new) + end + + def self.count + CouchPotato.database.view(self.by_name).size + end +end + +class CouchPotatoWidgetUsingDatabaseTwo + include CouchPotato::Persistence + + database_name = 'couch_potato_test_two' + + property :name + view :by_name, :key => :name + + + # mimic the AR interface used in example_steps + + def self.create!(attrs = {}) + CouchPotato.database.save(self.new) + end + + def self.count + CouchPotato.database.view(self.by_name).size + end +end diff --git a/vendor/gems/database_cleaner/examples/lib/datamapper_models.rb b/vendor/gems/database_cleaner/examples/lib/datamapper_models.rb new file mode 100644 index 0000000..896f347 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/lib/datamapper_models.rb @@ -0,0 +1,50 @@ +require "dm-core" +require "dm-transactions" + +#Datamapper 1.0 requires you to require dm-migrations to automigrate +require "dm-migrations" + +# only to please activerecord API used in database_cleaner/examples/features/step_definitions +# yes, i know that's lazy ... + +require "dm-validations" +require "dm-aggregates" + +DataMapper.setup(:default, "sqlite3:#{DB_DIR}/datamapper_default.db") +DataMapper.setup(:one, "sqlite3:#{DB_DIR}/datamapper_one.db") +DataMapper.setup(:two, "sqlite3:#{DB_DIR}/datamapper_two.db") + +class DataMapperWidget + include DataMapper::Resource + + property :id, Serial + property :name, String +end + +class DataMapperWidgetUsingDatabaseOne + include DataMapper::Resource + + def self.default_repository_name + :one + end + + property :id, Serial + property :name, String + +end + +class DataMapperWidgetUsingDatabaseTwo + include DataMapper::Resource + + def self.default_repository_name + :two + end + + property :id, Serial + property :name, String + +end + +DataMapperWidget.auto_migrate! +DataMapperWidgetUsingDatabaseOne.auto_migrate! +DataMapperWidgetUsingDatabaseTwo.auto_migrate! diff --git a/vendor/gems/database_cleaner/examples/lib/mongoid_models.rb b/vendor/gems/database_cleaner/examples/lib/mongoid_models.rb new file mode 100644 index 0000000..e31a127 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/lib/mongoid_models.rb @@ -0,0 +1,49 @@ +require 'mongoid' + +Mongoid.configure do |config| + name = 'database_cleaner_test' + config.master = Mongo::Connection.new.db(name) +end + + +#::MongoMapper.connection = Mongo::Connection.new('127.0.0.1') +#::MongoMapper.database = 'database_cleaner_test' + +class MongoidWidget + include Mongoid::Document + field :id, :type => Integer + field :name + + class << self + #mongoid doesn't seem to provide this... + def create!(*args) + new(*args).save! + end + end +end + +class MongoidWidgetUsingDatabaseOne + include Mongoid::Document + field :id, :type => Integer + field :name + + class << self + #mongoid doesn't seem to provide this... + def create!(*args) + new(*args).save! + end + end +end + +class MongoidWidgetUsingDatabaseTwo + include Mongoid::Document + field :id, :type => Integer + field :name + + class << self + #mongoid doesn't seem to provide this... + def create!(*args) + new(*args).save! + end + end +end diff --git a/vendor/gems/database_cleaner/examples/lib/mongomapper_models.rb b/vendor/gems/database_cleaner/examples/lib/mongomapper_models.rb new file mode 100644 index 0000000..667de31 --- /dev/null +++ b/vendor/gems/database_cleaner/examples/lib/mongomapper_models.rb @@ -0,0 +1,51 @@ +require 'mongo_mapper' + +::MongoMapper.connection = Mongo::Connection.new('127.0.0.1') +::MongoMapper.database = 'database_cleaner_test' + +class MongoMapperWidget + include MongoMapper::Document + key :id, Integer + key :name, String + + class << self + #mongomapper doesn't seem to provide this... + def create!(*args) + new(*args).save! + end + end +end + +class MongoMapperWidgetUsingDatabaseOne + include MongoMapper::Document + + connection = Mongo::Connection.new('127.0.0.1') + set_database_name = 'database_cleaner_test_one' + + key :id, Integer + key :name, String + + class << self + #mongomapper doesn't seem to provide this... + def create!(*args) + new(*args).save! + end + end +end + +class MongoMapperWidgetUsingDatabaseTwo + include MongoMapper::Document + + connection = Mongo::Connection.new('127.0.0.1') + set_database_name = 'database_cleaner_test_two' + + key :id, Integer + key :name, String + + class << self + #mongomapper doesn't seem to provide this... + def create!(*args) + new(*args).save! + end + end +end diff --git a/vendor/gems/database_cleaner/features/cleaning.feature b/vendor/gems/database_cleaner/features/cleaning.feature new file mode 100644 index 0000000..7602e88 --- /dev/null +++ b/vendor/gems/database_cleaner/features/cleaning.feature @@ -0,0 +1,22 @@ +Feature: database cleaning + In order to ease example and feature writing + As a developer + I want to have my database in a clean state + + Scenario Outline: ruby app + Given I am using + And the cleaning strategy + + When I run my scenarios that rely on a clean database + Then I should see all green + + Examples: + | ORM | Strategy | + | ActiveRecord | transaction | + | ActiveRecord | truncation | + | ActiveRecord | deletion | + | DataMapper | transaction | + | DataMapper | truncation | + | MongoMapper | truncation | + | Mongoid | truncation | + | CouchPotato | truncation | diff --git a/vendor/gems/database_cleaner/features/cleaning_default_strategy.feature b/vendor/gems/database_cleaner/features/cleaning_default_strategy.feature new file mode 100644 index 0000000..7f96ec3 --- /dev/null +++ b/vendor/gems/database_cleaner/features/cleaning_default_strategy.feature @@ -0,0 +1,19 @@ +Feature: database cleaning + In order to ease example and feature writing + As a developer + I want to have my database in a clean state with default strategy + + Scenario Outline: ruby app + Given I am using + And the default cleaning strategy + + When I run my scenarios that rely on a clean database + Then I should see all green + + Examples: + | ORM | + | ActiveRecord | + | DataMapper | + | MongoMapper | + | Mongoid | + | CouchPotato | diff --git a/vendor/gems/database_cleaner/features/cleaning_multiple_dbs.feature b/vendor/gems/database_cleaner/features/cleaning_multiple_dbs.feature new file mode 100644 index 0000000..23cc375 --- /dev/null +++ b/vendor/gems/database_cleaner/features/cleaning_multiple_dbs.feature @@ -0,0 +1,21 @@ +Feature: multiple database cleaning + In order to ease example and feature writing + As a developer + I want to have my databases in a clean state + + Scenario Outline: ruby app + Given I am using + And the cleaning strategy + + When I run my scenarios that rely on clean databases + Then I should see all green + + Examples: + | ORM | Strategy | + | ActiveRecord | truncation | + | ActiveRecord | deletion | + | DataMapper | truncation | + | MongoMapper | truncation | + | DataMapper | transaction | +# Not working... +#| ActiveRecord | transaction | diff --git a/vendor/gems/database_cleaner/features/cleaning_multiple_orms.feature b/vendor/gems/database_cleaner/features/cleaning_multiple_orms.feature new file mode 100644 index 0000000..ded7064 --- /dev/null +++ b/vendor/gems/database_cleaner/features/cleaning_multiple_orms.feature @@ -0,0 +1,29 @@ +Feature: database cleaning using multiple ORMs + In order to ease example and feature writing + As a developer + I want to have my database in a clean state + + Scenario Outline: ruby app + Given I am using and + + When I run my scenarios that rely on clean databases using multiple orms + Then I should see all green + + Examples: + | ORM1 | ORM2 | + | ActiveRecord | DataMapper | + | ActiveRecord | MongoMapper | + | ActiveRecord | Mongoid | + | ActiveRecord | CouchPotato | + | DataMapper | ActiveRecord | + | DataMapper | MongoMapper | + | DataMapper | Mongoid | + | DataMapper | CouchPotato | + | MongoMapper | ActiveRecord | + | MongoMapper | DataMapper | + | MongoMapper | Mongoid | + | MongoMapper | CouchPotato | + | CouchPotato | ActiveRecord | + | CouchPotato | DataMapper | + | CouchPotato | MongoMapper | + | CouchPotato | Mongoid | diff --git a/vendor/gems/database_cleaner/features/step_definitions/database_cleaner_steps.rb b/vendor/gems/database_cleaner/features/step_definitions/database_cleaner_steps.rb new file mode 100644 index 0000000..1663d0a --- /dev/null +++ b/vendor/gems/database_cleaner/features/step_definitions/database_cleaner_steps.rb @@ -0,0 +1,32 @@ + +Given /^I am using (ActiveRecord|DataMapper|MongoMapper|Mongoid|CouchPotato)$/ do |orm| + @feature_runner = FeatureRunner.new + @feature_runner.orm = orm +end + +Given /^I am using (ActiveRecord|DataMapper|MongoMapper|CouchPotato|Mongoid) and (ActiveRecord|DataMapper|MongoMapper|CouchPotato|Mongoid)$/ do |orm1,orm2| + @feature_runner = FeatureRunner.new + @feature_runner.orm = orm1 + @feature_runner.another_orm = orm2 +end + +Given /^the (.+) cleaning strategy$/ do |strategy| + @feature_runner.strategy = strategy +end + +When "I run my scenarios that rely on a clean database" do + @feature_runner.go 'example' +end + +When "I run my scenarios that rely on clean databases" do + @feature_runner.multiple_databases = true + @feature_runner.go 'example_multiple_db' +end + +When "I run my scenarios that rely on clean databases using multiple orms" do + @feature_runner.go 'example_multiple_orm' +end + +Then "I should see all green" do + fail "Feature failed with :#{@feature_runner.output}" unless @feature_runner.exit_status == 0 +end diff --git a/vendor/gems/database_cleaner/features/support/env.rb b/vendor/gems/database_cleaner/features/support/env.rb new file mode 100644 index 0000000..5c674ff --- /dev/null +++ b/vendor/gems/database_cleaner/features/support/env.rb @@ -0,0 +1,7 @@ +$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib') +require 'database_cleaner' + +require 'rspec/expectations' + +require 'test/unit/assertions' + diff --git a/vendor/gems/database_cleaner/features/support/feature_runner.rb b/vendor/gems/database_cleaner/features/support/feature_runner.rb new file mode 100644 index 0000000..f34ef0a --- /dev/null +++ b/vendor/gems/database_cleaner/features/support/feature_runner.rb @@ -0,0 +1,39 @@ +class FeatureRunner + attr_accessor :orm + attr_accessor :another_orm + attr_accessor :multiple_databases + attr_accessor :strategy + attr_accessor :exit_status + attr_accessor :output + + def strategy + @strategy || 'truncation' + end + + def go(feature) + full_dir ||= File.expand_path(File.dirname(__FILE__) + "/../../examples/") + Dir.chdir(full_dir) do + + + ENV['ORM'] = orm + ENV['STRATEGY'] = strategy + + if another_orm + ENV['ANOTHER_ORM'] = another_orm + else + ENV['ANOTHER_ORM'] = nil + end + + if multiple_databases + ENV['MULTIPLE_DBS'] = "true" + else + ENV['MULTIPLE_DBS'] = nil + end + + self.output = `#{"jruby -S " if defined?(JRUBY_VERSION)}cucumber features/#{feature}.feature` + + self.exit_status = $?.exitstatus + end + end + +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner.rb b/vendor/gems/database_cleaner/lib/database_cleaner.rb new file mode 100644 index 0000000..4356237 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner.rb @@ -0,0 +1,3 @@ +$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__))) +require 'database_cleaner/configuration' + diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/active_record/base.rb b/vendor/gems/database_cleaner/lib/database_cleaner/active_record/base.rb new file mode 100644 index 0000000..c097831 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/active_record/base.rb @@ -0,0 +1,53 @@ +require 'database_cleaner/generic/base' +require 'active_record' +require 'erb' + +module DatabaseCleaner + module ActiveRecord + + def self.available_strategies + %w[truncation transaction deletion] + end + + def self.config_file_location=(path) + @config_file_location = path + end + + def self.config_file_location + @config_file_location ||= "#{DatabaseCleaner.app_root}/config/database.yml" + end + + module Base + include ::DatabaseCleaner::Generic::Base + + attr_accessor :connection_hash + + def db=(desired_db) + @db = desired_db + load_config + end + + def db + @db || super + end + + def load_config + if self.db != :default && File.file?(ActiveRecord.config_file_location) + connection_details = YAML::load(ERB.new(IO.read(ActiveRecord.config_file_location)).result) + @connection_hash = connection_details[self.db.to_s] + end + end + + def create_connection_klass + Class.new(::ActiveRecord::Base) + end + + def connection_klass + return ::ActiveRecord::Base unless connection_hash + klass = create_connection_klass + klass.send :establish_connection, connection_hash + klass + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/active_record/deletion.rb b/vendor/gems/database_cleaner/lib/database_cleaner/active_record/deletion.rb new file mode 100644 index 0000000..397047c --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/active_record/deletion.rb @@ -0,0 +1,67 @@ +require 'active_record/base' +require 'active_record/connection_adapters/abstract_adapter' +require "database_cleaner/generic/truncation" +require 'database_cleaner/active_record/base' +require 'database_cleaner/active_record/truncation' +# This file may seem to have duplication with that of truncation, but by keeping them separate +# we avoiding loading this code when it is not being used (which is the common case). + +module ActiveRecord + module ConnectionAdapters + + class MysqlAdapter < MYSQL_ADAPTER_PARENT + def delete_table(table_name) + execute("DELETE FROM #{quote_table_name(table_name)};") + end + end + + class Mysql2Adapter < MYSQL2_ADAPTER_PARENT + def delete_table(table_name) + execute("DELETE FROM #{quote_table_name(table_name)};") + end + end + + class JdbcAdapter < AbstractAdapter + def delete_table(table_name) + execute("DELETE FROM #{quote_table_name(table_name)};") + end + end + + class PostgreSQLAdapter < AbstractAdapter + def delete_table(table_name) + execute("DELETE FROM #{quote_table_name(table_name)};") + end + end + + class SQLServerAdapter < AbstractAdapter + def delete_table(table_name) + execute("DELETE FROM #{quote_table_name(table_name)};") + end + end + + class OracleEnhancedAdapter < AbstractAdapter + def delete_table(table_name) + execute("DELETE FROM #{quote_table_name(table_name)}") + end + end + + end +end + + +module DatabaseCleaner::ActiveRecord + class Deletion < Truncation + + def clean + connection = connection_klass.connection + connection.disable_referential_integrity do + tables_to_truncate(connection).each do |table_name| + connection.delete_table table_name + end + end + end + + end +end + + diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/active_record/transaction.rb b/vendor/gems/database_cleaner/lib/database_cleaner/active_record/transaction.rb new file mode 100644 index 0000000..d19b82a --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/active_record/transaction.rb @@ -0,0 +1,31 @@ +require 'database_cleaner/active_record/base' +require 'database_cleaner/generic/transaction' + +module DatabaseCleaner::ActiveRecord + class Transaction + include ::DatabaseCleaner::ActiveRecord::Base + include ::DatabaseCleaner::Generic::Transaction + + def start + if connection_klass.connection.respond_to?(:increment_open_transactions) + connection_klass.connection.increment_open_transactions + else + connection_klass.__send__(:increment_open_transactions) + end + connection_klass.connection.begin_db_transaction + end + + + def clean + return unless connection_klass.connection.open_transactions > 0 + + connection_klass.connection.rollback_db_transaction + + if connection_klass.connection.respond_to?(:decrement_open_transactions) + connection_klass.connection.decrement_open_transactions + else + connection_klass.__send__(:decrement_open_transactions) + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/active_record/truncation.rb b/vendor/gems/database_cleaner/lib/database_cleaner/active_record/truncation.rb new file mode 100755 index 0000000..92346e4 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/active_record/truncation.rb @@ -0,0 +1,256 @@ +require 'active_record/base' + +require 'active_record/connection_adapters/abstract_adapter' + +require 'active_record/connection_adapters/abstract_mysql_adapter' rescue LoadError + +require "database_cleaner/generic/truncation" +require 'database_cleaner/active_record/base' + +module DatabaseCleaner + module ActiveRecord + + module AbstractAdapter + # used to be called views but that can clash with gems like schema_plus + # this gem is not meant to be exposing such an extra interface any way + def database_cleaner_view_cache + @views ||= select_values("select table_name from information_schema.views where table_schema = '#{current_database}'") rescue [] + end + + def database_cleaner_table_cache + # the adapters don't do caching (#130) but we make the assumption that the list stays the same in tests + @database_cleaner_tables ||= tables + end + + def truncate_table(table_name) + raise NotImplementedError + end + + def truncate_tables(tables) + tables.each do |table_name| + self.truncate_table(table_name) + end + end + end + + module MysqlAdapter + + def truncate_table(table_name) + execute("TRUNCATE TABLE #{quote_table_name(table_name)};") + end + + def truncate_tables(tables) + tables.each { |t| truncate_table(t) } + end + + def pre_count_truncate_tables(tables, options = {:reset_ids => true}) + filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?) + truncate_tables(tables.select(&filter)) + end + + private + + + def row_count(table) + select_value("SELECT EXISTS (SELECT 1 FROM #{quote_table_name(table)} LIMIT 1)") + end + + # Returns a boolean indicating if the given table has an auto-inc number higher than 0. + # Note, this is different than an empty table since an table may populated, the index increased, + # but then the table is cleaned. In other words, this function tells us if the given table + # was ever inserted into. + def has_been_used?(table) + if row_count(table) > 0 + true + else + select_value(<<-SQL) > 1 # returns nil if not present + SELECT Auto_increment + FROM information_schema.tables + WHERE table_name='#{table}'; + SQL + end + end + + def has_rows?(table) + row_count(table) > 0 + end + end + + + module IBM_DBAdapter + def truncate_table(table_name) + execute("TRUNCATE #{quote_table_name(table_name)} IMMEDIATE") + end + end + + + module SQLiteAdapter + def delete_table(table_name) + execute("DELETE FROM #{quote_table_name(table_name)};") + execute("DELETE FROM sqlite_sequence where name = '#{table_name}';") + end + alias truncate_table delete_table + end + + module TruncateOrDelete + def truncate_table(table_name) + begin + execute("TRUNCATE TABLE #{quote_table_name(table_name)};") + rescue ActiveRecord::StatementInvalid + execute("DELETE FROM #{quote_table_name(table_name)};") + end + end + end + + module PostgreSQLAdapter + def db_version + @db_version ||= postgresql_version + end + + def cascade + @cascade ||= db_version >= 80200 ? 'CASCADE' : '' + end + + def restart_identity + @restart_identity ||= db_version >= 80400 ? 'RESTART IDENTITY' : '' + end + + def truncate_table(table_name) + truncate_tables([table_name]) + end + + def truncate_tables(table_names) + return if table_names.nil? || table_names.empty? + execute("TRUNCATE TABLE #{table_names.map{|name| quote_table_name(name)}.join(', ')} #{restart_identity} #{cascade};") + end + + def pre_count_truncate_tables(tables, options = {:reset_ids => true}) + filter = options[:reset_ids] ? method(:has_been_used?) : method(:has_rows?) + truncate_tables(tables.select(&filter)) + end + + private + + # Returns a boolean indicating if the given table has an auto-inc number higher than 0. + # Note, this is different than an empty table since an table may populated, the index increased, + # but then the table is cleaned. In other words, this function tells us if the given table + # was ever inserted into. + def has_been_used?(table) + cur_val = select_value("SELECT currval('#{table}_id_seq');").to_i rescue ActiveRecord::StatementInvalid + cur_val && cur_val > 0 + end + + def has_rows?(table) + select_value("SELECT true FROM #{table} LIMIT 1;") + end + end + + module OracleEnhancedAdapter + def truncate_table(table_name) + execute("TRUNCATE TABLE #{quote_table_name(table_name)}") + end + end + + end +end + +#TODO: Remove monkeypatching and decorate the connection instead! + +module ActiveRecord + module ConnectionAdapters + # Activerecord-jdbc-adapter defines class dependencies a bit differently - if it is present, confirm to ArJdbc hierarchy to avoid 'superclass mismatch' errors. + USE_ARJDBC_WORKAROUND = defined?(ArJdbc) + + class AbstractAdapter + include ::DatabaseCleaner::ActiveRecord::AbstractAdapter + end + + unless USE_ARJDBC_WORKAROUND + class SQLiteAdapter < AbstractAdapter + end + end + + # ActiveRecord 3.1 support + if defined?(AbstractMysqlAdapter) + MYSQL_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractMysqlAdapter + MYSQL2_ADAPTER_PARENT = AbstractMysqlAdapter + else + MYSQL_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter + MYSQL2_ADAPTER_PARENT = AbstractAdapter + end + + SQLITE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter + POSTGRE_ADAPTER_PARENT = USE_ARJDBC_WORKAROUND ? JdbcAdapter : AbstractAdapter + + class MysqlAdapter < MYSQL_ADAPTER_PARENT + include ::DatabaseCleaner::ActiveRecord::MysqlAdapter + end + + class Mysql2Adapter < MYSQL2_ADAPTER_PARENT + include ::DatabaseCleaner::ActiveRecord::MysqlAdapter + end + + class IBM_DBAdapter < AbstractAdapter + include ::DatabaseCleaner::ActiveRecord::IBM_DBAdapter + end + + class SQLite3Adapter < SQLITE_ADAPTER_PARENT + include ::DatabaseCleaner::ActiveRecord::SQLiteAdapter + end + + class JdbcAdapter < AbstractAdapter + include ::DatabaseCleaner::ActiveRecord::TruncateOrDelete + end + + class PostgreSQLAdapter < POSTGRE_ADAPTER_PARENT + include ::DatabaseCleaner::ActiveRecord::PostgreSQLAdapter + end + + class SQLServerAdapter < AbstractAdapter + include ::DatabaseCleaner::ActiveRecord::TruncateOrDelete + end + + class OracleEnhancedAdapter < AbstractAdapter + include ::DatabaseCleaner::ActiveRecord::OracleEnhancedAdapter + end + + end +end + + +module DatabaseCleaner::ActiveRecord + class Truncation + include ::DatabaseCleaner::ActiveRecord::Base + include ::DatabaseCleaner::Generic::Truncation + + def clean + connection = connection_klass.connection + connection.disable_referential_integrity do + if pre_count? && connection.respond_to?(:pre_count_truncate_tables) + connection.pre_count_truncate_tables(tables_to_truncate(connection), {:reset_ids => reset_ids?}) + else + connection.truncate_tables(tables_to_truncate(connection)) + end + end + end + + private + + def tables_to_truncate(connection) + (@only || connection.database_cleaner_table_cache) - @tables_to_exclude - connection.database_cleaner_view_cache + end + + # overwritten + def migration_storage_name + 'schema_migrations' + end + + def pre_count? + @pre_count == true + end + + def reset_ids? + @reset_ids != false + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/base.rb b/vendor/gems/database_cleaner/lib/database_cleaner/base.rb new file mode 100644 index 0000000..eb3cd8c --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/base.rb @@ -0,0 +1,138 @@ +require 'database_cleaner/null_strategy' +module DatabaseCleaner + class Base + + def initialize(desired_orm = nil,opts = {}) + if [:autodetect, nil, "autodetect"].include?(desired_orm) + autodetect + else + self.orm = desired_orm + end + self.db = opts[:connection] if opts.has_key? :connection + set_default_orm_strategy + end + + def db=(desired_db) + self.strategy_db = desired_db + @db = desired_db + end + + def strategy_db=(desired_db) + if strategy.respond_to? :db= + strategy.db = desired_db + elsif desired_db!= :default + raise ArgumentError, "You must provide a strategy object that supports non default databases when you specify a database" + end + end + + def db + @db || :default + end + + def create_strategy(*args) + strategy, *strategy_args = args + orm_strategy(strategy).new(*strategy_args) + end + + def clean_with(*args) + strategy = create_strategy(*args) + strategy.clean + strategy + end + + alias clean_with! clean_with + + def strategy=(args) + strategy, *strategy_args = args + if strategy.is_a?(Symbol) + @strategy = create_strategy(*args) + elsif strategy_args.empty? + @strategy = strategy + else + raise ArgumentError, "You must provide a strategy object, or a symbol for a known strategy along with initialization params." + end + + self.strategy_db = self.db + + @strategy + end + + def strategy + @strategy || NullStrategy + end + + def orm=(desired_orm) + @orm = desired_orm.to_sym + end + + def orm + @orm || autodetect + end + + def start + strategy.start + end + + def clean + strategy.clean + end + + alias clean! clean + + def auto_detected? + !!@autodetected + end + + #TODO make strategies directly comparable + def ==(other) + self.orm == other.orm && self.db == other.db && self.strategy.class == other.strategy.class + end + + private + + def orm_module + ::DatabaseCleaner.orm_module(orm) + end + + def orm_strategy(strategy) + require "database_cleaner/#{orm.to_s}/#{strategy.to_s}" + orm_module.const_get(strategy.to_s.capitalize) + rescue LoadError => e + if orm_module.respond_to? :available_strategies + raise UnknownStrategySpecified, "The '#{strategy}' strategy does not exist for the #{orm} ORM! Available strategies: #{orm_module.available_strategies.join(', ')}" + else + raise UnknownStrategySpecified, "The '#{strategy}' strategy does not exist for the #{orm} ORM!" + end + end + + def autodetect + @orm ||= begin + @autodetected = true + if defined? ::ActiveRecord + :active_record + elsif defined? ::DataMapper + :data_mapper + elsif defined? ::MongoMapper + :mongo_mapper + elsif defined? ::Mongoid + :mongoid + elsif defined? ::CouchPotato + :couch_potato + elsif defined? ::Sequel + :sequel + else + raise NoORMDetected, "No known ORM was detected! Is ActiveRecord, DataMapper, Sequel, MongoMapper, Mongoid, or CouchPotato loaded?" + end + end + end + + def set_default_orm_strategy + case orm + when :active_record, :data_mapper, :sequel + self.strategy = :transaction + when :mongo_mapper, :mongoid, :couch_potato + self.strategy = :truncation + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/configuration.rb b/vendor/gems/database_cleaner/lib/database_cleaner/configuration.rb new file mode 100644 index 0000000..1fc964f --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/configuration.rb @@ -0,0 +1,96 @@ +require 'database_cleaner/base' + +module DatabaseCleaner + + class NoORMDetected < StandardError; end + class UnknownStrategySpecified < ArgumentError; end + + class << self + def [](orm,opts = {}) + raise NoORMDetected unless orm + @connections ||= [] + cleaner = DatabaseCleaner::Base.new(orm,opts) + connections.push cleaner + cleaner + end + + def app_root=(desired_root) + @app_root = desired_root + end + + def app_root + @app_root || Dir.pwd + end + + def connections + @connections ||= [::DatabaseCleaner::Base.new] + end + + def logger=(log_source) + @logger = log_source + end + + def logger + return @logger if @logger + + @logger = Logger.new(STDOUT) + @logger.level = Logger::ERROR + @logger + end + + def strategy=(stratagem) + connections.each { |connect| connect.strategy = stratagem } + remove_duplicates + end + + def orm=(orm) + connections.each { |connect| connect.orm = orm } + remove_duplicates + end + + def start + connections.each { |connection| connection.start } + end + + def clean + connections.each { |connection| connection.clean } + end + + alias clean! clean + + def clean_with(*args) + connections.each { |connection| connection.clean_with(*args) } + end + + alias clean_with! clean_with + + def remove_duplicates + temp = [] + connections.each do |connect| + temp.push connect unless temp.include? connect + end + @connections = temp + end + + def orm_module(symbol) + case symbol + when :active_record + DatabaseCleaner::ActiveRecord + when :data_mapper + DatabaseCleaner::DataMapper + when :mongo + DatabaseCleaner::Mongo + when :mongoid + DatabaseCleaner::Mongoid + when :mongo_mapper + DatabaseCleaner::MongoMapper + when :moped + DatabaseCleaner::Moped + when :couch_potato + DatabaseCleaner::CouchPotato + when :sequel + DatabaseCleaner::Sequel + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/couch_potato/base.rb b/vendor/gems/database_cleaner/lib/database_cleaner/couch_potato/base.rb new file mode 100644 index 0000000..b53a52a --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/couch_potato/base.rb @@ -0,0 +1,7 @@ +module DatabaseCleaner + module CouchPotato + def self.available_strategies + %w[truncation] + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/couch_potato/truncation.rb b/vendor/gems/database_cleaner/lib/database_cleaner/couch_potato/truncation.rb new file mode 100644 index 0000000..5a163aa --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/couch_potato/truncation.rb @@ -0,0 +1,28 @@ +require 'database_cleaner/generic/truncation' + +module DatabaseCleaner + module CouchPotato + class Truncation + include ::DatabaseCleaner::Generic::Truncation + + def initialize(options = {}) + if options.has_key?(:only) || options.has_key?(:except) + raise ArgumentError, "The :only and :except options are not available for use with CouchPotato/CouchDB." + elsif !options.empty? + raise ArgumentError, "Unsupported option. You specified #{options.keys.join(',')}." + end + super + end + + def clean + database.recreate! + end + + private + + def database + ::CouchPotato.couchrest_database + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/cucumber.rb b/vendor/gems/database_cleaner/lib/database_cleaner/cucumber.rb new file mode 100644 index 0000000..33ee62c --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/cucumber.rb @@ -0,0 +1,11 @@ +Before do + DatabaseCleaner.start +end + +After do + begin + DatabaseCleaner.clean + rescue Exception => e + DatabaseCleaner.logger.error "Exception encountered by DatabaseCleaner in Cucumber After block: #{e}" + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/base.rb b/vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/base.rb new file mode 100644 index 0000000..7481e7c --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/base.rb @@ -0,0 +1,21 @@ +require 'database_cleaner/generic/base' +module DatabaseCleaner + module DataMapper + def self.available_strategies + %w[truncation transaction] + end + + module Base + include ::DatabaseCleaner::Generic::Base + + def db=(desired_db) + @db = desired_db + end + + def db + @db || :default + end + + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/transaction.rb b/vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/transaction.rb new file mode 100644 index 0000000..31b3287 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/transaction.rb @@ -0,0 +1,28 @@ +require 'database_cleaner/data_mapper/base' +require 'database_cleaner/generic/transaction' + +module DatabaseCleaner::DataMapper + class Transaction + include ::DatabaseCleaner::DataMapper::Base + include ::DatabaseCleaner::Generic::Transaction + + def start(repository = self.db) + ::DataMapper.repository(repository) do |r| + transaction = DataMapper::Transaction.new(r) + transaction.begin + r.adapter.push_transaction(transaction) + end + end + + def clean(repository = self.db) + ::DataMapper.repository(repository) do |r| + adapter = r.adapter + while adapter.current_transaction + adapter.current_transaction.rollback + adapter.pop_transaction + end + end + end + + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/truncation.rb b/vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/truncation.rb new file mode 100644 index 0000000..6625558 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/data_mapper/truncation.rb @@ -0,0 +1,175 @@ +require "database_cleaner/generic/truncation" +require 'database_cleaner/data_mapper/base' + +module DataMapper + module Adapters + + class DataObjectsAdapter + + def storage_names(repository = :default) + raise NotImplementedError + end + + end + + class MysqlAdapter < DataObjectsAdapter + + # taken from http://github.com/godfat/dm-mapping/tree/master + def storage_names(repository = :default) + select 'SHOW TABLES' + end + + def truncate_table(table_name) + execute("TRUNCATE TABLE #{quote_name(table_name)};") + end + + # copied from activerecord + def disable_referential_integrity + old = select("SELECT @@FOREIGN_KEY_CHECKS;") + begin + execute("SET FOREIGN_KEY_CHECKS = 0;") + yield + ensure + execute("SET FOREIGN_KEY_CHECKS = ?", *old) + end + end + + end + + + class Sqlite3Adapter < DataObjectsAdapter + + # taken from http://github.com/godfat/dm-mapping/tree/master + def storage_names(repository = :default) + # activerecord-2.1.0/lib/active_record/connection_adapters/sqlite_adapter.rb: 177 + sql = <<-SQL + SELECT name + FROM sqlite_master + WHERE type = 'table' AND NOT name = 'sqlite_sequence' + SQL + # activerecord-2.1.0/lib/active_record/connection_adapters/sqlite_adapter.rb: 181 + select(sql) + end + + def truncate_table(table_name) + execute("DELETE FROM #{quote_name(table_name)};") + execute("DELETE FROM sqlite_sequence where name = '#{table_name}';") + end + + # this is a no-op copied from activerecord + # i didn't find out if/how this is possible + # activerecord also doesn't do more here + def disable_referential_integrity + yield + end + + end + + class SqliteAdapter < DataObjectsAdapter + # taken from http://github.com/godfat/dm-mapping/tree/master + def storage_names(repository = :default) + # activerecord-2.1.0/lib/active_record/connection_adapters/sqlite_adapter.rb: 177 + sql = <<-SQL + SELECT name + FROM sqlite_master + WHERE type = 'table' AND NOT name = 'sqlite_sequence' + SQL + # activerecord-2.1.0/lib/active_record/connection_adapters/sqlite_adapter.rb: 181 + select(sql) + end + + def truncate_table(table_name) + execute("DELETE FROM #{quote_name(table_name)};") + execute("DELETE FROM sqlite_sequence where name = '#{table_name}';") + end + + # this is a no-op copied from activerecord + # i didn't find out if/how this is possible + # activerecord also doesn't do more here + def disable_referential_integrity + yield + end + + end + + # FIXME + # i don't know if this works + # i basically just copied activerecord code to get a rough idea what they do. + # i don't have postgres available, so i won't be the one to write this. + # maybe codes below gets some postgres/datamapper user going, though. + class PostgresAdapter < DataObjectsAdapter + + # taken from http://github.com/godfat/dm-mapping/tree/master + def storage_names(repository = :default) + sql = <<-SQL + SELECT table_name FROM "information_schema"."tables" + WHERE table_schema = current_schema() and table_type = 'BASE TABLE' + SQL + select(sql) + end + + def truncate_table(table_name) + execute("TRUNCATE TABLE #{quote_name(table_name)} RESTART IDENTITY CASCADE;") + end + + # FIXME + # copied from activerecord + def supports_disable_referential_integrity? + version = select("SHOW server_version")[0][0].split('.') + (version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false + rescue + return false + end + + # FIXME + # copied unchanged from activerecord + def disable_referential_integrity(repository = :default) + if supports_disable_referential_integrity? then + execute(storage_names(repository).collect do |name| + "ALTER TABLE #{quote_name(name)} DISABLE TRIGGER ALL" + end.join(";")) + end + yield + ensure + if supports_disable_referential_integrity? then + execute(storage_names(repository).collect do |name| + "ALTER TABLE #{quote_name(name)} ENABLE TRIGGER ALL" + end.join(";")) + end + end + + end + + end +end + + +module DatabaseCleaner + module DataMapper + class Truncation + include ::DatabaseCleaner::DataMapper::Base + include ::DatabaseCleaner::Generic::Truncation + + def clean(repository = self.db) + adapter = ::DataMapper.repository(repository).adapter + adapter.disable_referential_integrity do + tables_to_truncate(repository).each do |table_name| + adapter.truncate_table table_name + end + end + end + + private + + def tables_to_truncate(repository = self.db) + (@only || ::DataMapper.repository(repository).adapter.storage_names(repository)) - @tables_to_exclude + end + + # overwritten + def migration_storage_name + 'migration_info' + end + + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/generic/base.rb b/vendor/gems/database_cleaner/lib/database_cleaner/generic/base.rb new file mode 100644 index 0000000..ca7e5e7 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/generic/base.rb @@ -0,0 +1,20 @@ +module ::DatabaseCleaner + module Generic + module Base + + def self.included(base) + base.extend(ClassMethods) + end + + def db + :default + end + + module ClassMethods + def available_strategies + %W[] + end + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/generic/transaction.rb b/vendor/gems/database_cleaner/lib/database_cleaner/generic/transaction.rb new file mode 100644 index 0000000..4c8ace4 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/generic/transaction.rb @@ -0,0 +1,11 @@ +module DatabaseCleaner + module Generic + module Transaction + def initialize(opts = {}) + if !opts.empty? + raise ArgumentError, "Options are not available for transaction strategies." + end + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/generic/truncation.rb b/vendor/gems/database_cleaner/lib/database_cleaner/generic/truncation.rb new file mode 100644 index 0000000..2e41c1a --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/generic/truncation.rb @@ -0,0 +1,39 @@ +module DatabaseCleaner + module Generic + module Truncation + def initialize(opts={}) + if !opts.empty? && !(opts.keys - [:only, :except, :pre_count, :reset_ids]).empty? + raise ArgumentError, "The only valid options are :only, :except, :pre_count or :reset_ids. You specified #{opts.keys.join(',')}." + end + if opts.has_key?(:only) && opts.has_key?(:except) + raise ArgumentError, "You may only specify either :only or :except. Doing both doesn't really make sense does it?" + end + + @only = opts[:only] + @tables_to_exclude = (opts[:except] || []).dup + @tables_to_exclude << migration_storage_name if migration_storage_name + @pre_count = opts[:pre_count] + @reset_ids = opts[:reset_ids] + end + + def start + #included for compatability reasons, do nothing if you don't need to + end + + def clean + raise NotImplementedError + end + + private + def tables_to_truncate + raise NotImplementedError + end + + # overwrite in subclasses + # default implementation given because migration storage need not be present + def migration_storage_name + nil + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/mongo/truncation.rb b/vendor/gems/database_cleaner/lib/database_cleaner/mongo/truncation.rb new file mode 100644 index 0000000..13790f1 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/mongo/truncation.rb @@ -0,0 +1,22 @@ +module DatabaseCleaner + module Mongo + module Truncation + + def clean + if @only + collections.each { |c| c.remove if @only.include?(c.name) } + else + collections.each { |c| c.remove unless @tables_to_exclude.include?(c.name) } + end + true + end + + private + + def collections + database.collections.select { |c| c.name !~ /^system\./ } + end + + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/mongo_mapper/base.rb b/vendor/gems/database_cleaner/lib/database_cleaner/mongo_mapper/base.rb new file mode 100644 index 0000000..4ea7be7 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/mongo_mapper/base.rb @@ -0,0 +1,20 @@ +require 'database_cleaner/generic/base' +module DatabaseCleaner + module MongoMapper + def self.available_strategies + %w[truncation] + end + + module Base + include ::DatabaseCleaner::Generic::Base + + def db=(desired_db) + @db = desired_db + end + + def db + @db || :default + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/mongo_mapper/truncation.rb b/vendor/gems/database_cleaner/lib/database_cleaner/mongo_mapper/truncation.rb new file mode 100644 index 0000000..5d2d64f --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/mongo_mapper/truncation.rb @@ -0,0 +1,19 @@ +require 'database_cleaner/mongo_mapper/base' +require 'database_cleaner/generic/truncation' +require 'database_cleaner/mongo/truncation' + +module DatabaseCleaner + module MongoMapper + class Truncation + include ::DatabaseCleaner::MongoMapper::Base + include ::DatabaseCleaner::Generic::Truncation + include ::DatabaseCleaner::Mongo::Truncation + + private + + def database + ::MongoMapper.database + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/mongoid/base.rb b/vendor/gems/database_cleaner/lib/database_cleaner/mongoid/base.rb new file mode 100644 index 0000000..417d466 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/mongoid/base.rb @@ -0,0 +1,20 @@ +require 'database_cleaner/generic/base' +module DatabaseCleaner + module Mongoid + def self.available_strategies + %w[truncation] + end + + module Base + include ::DatabaseCleaner::Generic::Base + + def db=(desired_db) + @db = desired_db + end + + def db + @db || :default + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/mongoid/truncation.rb b/vendor/gems/database_cleaner/lib/database_cleaner/mongoid/truncation.rb new file mode 100644 index 0000000..6459ef9 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/mongoid/truncation.rb @@ -0,0 +1,37 @@ +require 'database_cleaner/mongoid/base' +require 'database_cleaner/generic/truncation' +require 'database_cleaner/mongo/truncation' +require 'database_cleaner/moped/truncation' +require 'mongoid/version' + +module DatabaseCleaner + module Mongoid + class Truncation + include ::DatabaseCleaner::Mongoid::Base + include ::DatabaseCleaner::Generic::Truncation + + if ::Mongoid::VERSION < '3' + + include ::DatabaseCleaner::Mongo::Truncation + + private + + def database + ::Mongoid.database + end + + else + + include ::DatabaseCleaner::Moped::Truncation + + private + + def session + ::Mongoid.default_session + end + + end + + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/moped/truncation.rb b/vendor/gems/database_cleaner/lib/database_cleaner/moped/truncation.rb new file mode 100644 index 0000000..f1f0f8f --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/moped/truncation.rb @@ -0,0 +1,25 @@ +module DatabaseCleaner + module Moped + module Truncation + + def clean + if @only + collections.each { |c| session[c].find.remove_all if @only.include?(c) } + else + collections.each { |c| session[c].find.remove_all unless @tables_to_exclude.include?(c) } + end + true + end + + private + + def collections + session['system.namespaces'].find(:name => { '$not' => /system|\$/ }).to_a.map do |collection| + _, name = collection['name'].split('.', 2) + name + end + end + + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/null_strategy.rb b/vendor/gems/database_cleaner/lib/database_cleaner/null_strategy.rb new file mode 100644 index 0000000..eb67c97 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/null_strategy.rb @@ -0,0 +1,15 @@ +module DatabaseCleaner + class NullStrategy + def self.start + # no-op + end + + def self.db=(connection) + # no-op + end + + def self.clean + # no-op + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/sequel/base.rb b/vendor/gems/database_cleaner/lib/database_cleaner/sequel/base.rb new file mode 100644 index 0000000..77d03f1 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/sequel/base.rb @@ -0,0 +1,22 @@ +require 'database_cleaner/generic/base' +module DatabaseCleaner + module Sequel + def self.available_strategies + %w[truncation transaction] + end + + module Base + include ::DatabaseCleaner::Generic::Base + + def db=(desired_db) + @db = desired_db + end + + def db + return @db if @db && @db != :default + raise "As you have more than one active sequel database you have to specify the one to use manually!" if ::Sequel::DATABASES.count > 1 + ::Sequel::DATABASES.first || :default + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/sequel/transaction.rb b/vendor/gems/database_cleaner/lib/database_cleaner/sequel/transaction.rb new file mode 100644 index 0000000..18bf826 --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/sequel/transaction.rb @@ -0,0 +1,25 @@ +require 'database_cleaner/sequel/base' +module DatabaseCleaner + module Sequel + class Transaction + include ::DatabaseCleaner::Sequel::Base + + def start + @fibers||= [] + db= self.db + f= Fiber.new do + db.transaction(:rollback => :always, :savepoint => true) do + Fiber.yield + end + end + f.resume + @fibers<< f + end + + def clean + f= @fibers.pop + f.resume + end + end + end +end diff --git a/vendor/gems/database_cleaner/lib/database_cleaner/sequel/truncation.rb b/vendor/gems/database_cleaner/lib/database_cleaner/sequel/truncation.rb new file mode 100755 index 0000000..982ed2f --- /dev/null +++ b/vendor/gems/database_cleaner/lib/database_cleaner/sequel/truncation.rb @@ -0,0 +1,50 @@ +require "database_cleaner/generic/truncation" +require 'database_cleaner/sequel/base' + +module DatabaseCleaner + module Sequel + class Truncation + include ::DatabaseCleaner::Sequel::Base + include ::DatabaseCleaner::Generic::Truncation + + def clean + case db.database_type + when :postgres + # PostgreSQL requires all tables with FKs to be truncates in the same command, or have the CASCADE keyword + # appended. Bulk truncation without CASCADE is: + # * Safer. Tables outside of tables_to_truncate won't be affected. + # * Faster. Less roundtrips to the db. + unless (tables= tables_to_truncate(db)).empty? + all_tables= tables.map{|t| %["#{t}"]}.join ',' + db.run "TRUNCATE TABLE #{all_tables};" + end + else + # Truncate each table normally + each_table do |db, table| + db[table].truncate + end + end + end + + def each_table + tables_to_truncate(db).each do |table| + yield db, table + end + end + + private + + def tables_to_truncate(db) + (@only || db.tables) - @tables_to_exclude + end + + # overwritten + def migration_storage_name + :schema_info + end + + end + end +end + + diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/active_record/base_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/base_spec.rb new file mode 100644 index 0000000..2ecb94c --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/base_spec.rb @@ -0,0 +1,144 @@ +require 'spec_helper' +require 'active_record' +require 'database_cleaner/active_record/base' +require 'database_cleaner/shared_strategy' + +module DatabaseCleaner + describe ActiveRecord do + it { should respond_to(:available_strategies) } + + describe "config_file_location" do + subject { ActiveRecord.config_file_location } + + it "should default to DatabaseCleaner.root / config / database.yml" do + ActiveRecord.config_file_location=nil + DatabaseCleaner.should_receive(:app_root).and_return("/path/to") + subject.should == '/path/to/config/database.yml' + end + end + + end + + module ActiveRecord + class ExampleStrategy + include ::DatabaseCleaner::ActiveRecord::Base + end + + describe ExampleStrategy do + let :config_location do + '/path/to/config/database.yml' + end + + before { ::DatabaseCleaner::ActiveRecord.stub(:config_file_location).and_return(config_location) } + + it_should_behave_like "a generic strategy" + + describe "db" do + + it "should store my desired db" do + subject.stub(:load_config) + + subject.db = :my_db + subject.db.should == :my_db + end + + it "should default to :default" do + subject.db.should == :default + end + + it "should load_config when I set db" do + subject.should_receive(:load_config) + subject.db = :my_db + end + end + + describe "load_config" do + + before do + subject.db = :my_db + yaml = <<-Y +my_db: + database: <%= "ONE".downcase %> + Y + File.stub(:file?).with(config_location).and_return(true) + IO.stub(:read).with(config_location).and_return(yaml) + end + + it "should parse the config" do + YAML.should_receive(:load).and_return( {:nil => nil} ) + subject.load_config + end + + it "should process erb in the config" do + transformed = <<-Y +my_db: + database: one + Y + YAML.should_receive(:load).with(transformed).and_return({ "my_db" => {"database" => "one"} }) + subject.load_config + end + + it "should store the relevant config in connection_hash" do + subject.load_config + subject.connection_hash.should == {"database" => "one"} + end + + it "should skip config if config file is not available" do + File.should_receive(:file?).with(config_location).and_return(false) + subject.load_config + subject.connection_hash.should be_blank + end + + it "skips the file when the db is set to :default" do + # to avoid https://github.com/bmabey/database_cleaner/issues/72 + subject.db = :default + YAML.should_not_receive(:load) + subject.load_config + end + + end + + describe "connection_hash" do + it "should store connection_hash" do + subject.connection_hash = { :key => "value" } + subject.connection_hash.should == { :key => "value" } + end + end + + describe "create_connection_klass" do + it "should return a class" do + subject.create_connection_klass.should be_a(Class) + end + + it "should return a class extending ::ActiveRecord::Base" do + subject.create_connection_klass.ancestors.should include(::ActiveRecord::Base) + end + end + + describe "connection_klass" do + it { expect{ subject.connection_klass }.to_not raise_error } + it "should default to ActiveRecord::Base" do + subject.connection_klass.should == ::ActiveRecord::Base + end + + context "when connection_hash is set" do + let(:hash) { mock("hash") } + before { subject.stub(:connection_hash).and_return(hash) } + + it "should create connection_klass if it doesnt exist if connection_hash is set" do + subject.should_receive(:create_connection_klass).and_return(mock('class').as_null_object) + subject.connection_klass + end + + it "should configure the class from create_connection_klass if connection_hash is set" do + klass = mock('klass') + klass.should_receive(:establish_connection).with(hash) + + subject.should_receive(:create_connection_klass).and_return(klass) + subject.connection_klass + end + end + end + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/active_record/transaction_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/transaction_spec.rb new file mode 100644 index 0000000..73ea217 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/transaction_spec.rb @@ -0,0 +1,77 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'database_cleaner/active_record/transaction' +require 'active_record' + +module DatabaseCleaner + module ActiveRecord + + describe Transaction do + let (:connection) { mock("connection") } + before(:each) do + ::ActiveRecord::Base.stub!(:connection).and_return(connection) + end + + describe "#start" do + it "should increment open transactions if possible" do + connection.stub!(:respond_to?).with(:increment_open_transactions).and_return(true) + connection.stub!(:begin_db_transaction) + + connection.should_receive(:increment_open_transactions) + Transaction.new.start + end + + it "should tell ActiveRecord to increment connection if its not possible to increment current connection" do + connection.stub!(:respond_to?).with(:increment_open_transactions).and_return(false) + connection.stub!(:begin_db_transaction) + + ::ActiveRecord::Base.should_receive(:increment_open_transactions) + Transaction.new.start + end + + it "should start a transaction" do + connection.stub!(:increment_open_transactions) + + connection.should_receive(:begin_db_transaction) + Transaction.new.start + end + end + + describe "#clean" do + it "should start a transaction" do + connection.should_receive(:open_transactions).and_return(1) + + connection.stub!(:decrement_open_transactions) + + connection.should_receive(:rollback_db_transaction) + Transaction.new.clean + end + + it "should decrement open transactions if possible" do + connection.should_receive(:open_transactions).and_return(1) + + connection.stub!(:respond_to?).with(:decrement_open_transactions).and_return(true) + connection.stub!(:rollback_db_transaction) + + connection.should_receive(:decrement_open_transactions) + Transaction.new.clean + end + + it "should not try to decrement or rollback if open_transactions is 0 for whatever reason" do + connection.should_receive(:open_transactions).and_return(0) + + Transaction.new.clean + end + + it "should decrement connection via ActiveRecord::Base if connection won't" do + connection.should_receive(:open_transactions).and_return(1) + connection.stub!(:respond_to?).with(:decrement_open_transactions).and_return(false) + connection.stub!(:rollback_db_transaction) + + ::ActiveRecord::Base.should_receive(:decrement_open_transactions) + Transaction.new.clean + end + end + end + + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/mysql2_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/mysql2_spec.rb new file mode 100644 index 0000000..29569a2 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/mysql2_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' +require 'active_record' +require 'support/active_record/mysql2_setup' +require 'database_cleaner/active_record/truncation' +require 'database_cleaner/active_record/truncation/shared_fast_truncation' + +module ActiveRecord + module ConnectionAdapters + describe do + before(:all) { active_record_mysql2_setup } + + let(:adapter) { Mysql2Adapter } + let(:connection) { active_record_mysql2_connection } + + describe "#truncate_table" do + it "should truncate the table" do + 2.times { User.create } + + connection.truncate_table('users') + User.count.should == 0 + end + + it "should reset AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.truncate_table('users') + + User.create.id.should == 1 + end + end + + it_behaves_like "an adapter with pre-count truncation" do + let(:adapter) { Mysql2Adapter } + let(:connection) { active_record_mysql2_connection } + end + end + end +end + diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/mysql_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/mysql_spec.rb new file mode 100644 index 0000000..01b34c0 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/mysql_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' +require 'active_record' +require 'support/active_record/mysql_setup' +require 'database_cleaner/active_record/truncation' +require 'database_cleaner/active_record/truncation/shared_fast_truncation' + +module ActiveRecord + module ConnectionAdapters + describe do + before(:all) { active_record_mysql_setup } + + let(:adapter) { MysqlAdapter } + let(:connection) { active_record_mysql_connection } + + describe "#truncate_table" do + it "should truncate the table" do + 2.times { User.create } + + connection.truncate_table('users') + User.count.should == 0 + end + + it "should reset AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.truncate_table('users') + + User.create.id.should == 1 + end + end + + it_behaves_like "an adapter with pre-count truncation" do + let(:adapter) { MysqlAdapter } + let(:connection) { active_record_mysql_connection } + end + end + end +end + diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/postgresql_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/postgresql_spec.rb new file mode 100644 index 0000000..64c242d --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/postgresql_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' +require 'active_record' +require 'support/active_record/postgresql_setup' +require 'database_cleaner/active_record/truncation' +require 'database_cleaner/active_record/truncation/shared_fast_truncation' + +module ActiveRecord + module ConnectionAdapters + describe do + before(:all) { active_record_pg_setup } + + let(:adapter) { PostgreSQLAdapter } + + let(:connection) do + active_record_pg_connection + end + + before(:each) do + connection.truncate_tables connection.tables + end + + describe "#truncate_table" do + it "truncates the table" do + 2.times { User.create } + + connection.truncate_table('users') + User.count.should == 0 + end + + it "resets AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.truncate_table('users') + + User.create.id.should == 1 + end + end + + it_behaves_like "an adapter with pre-count truncation" do + let(:adapter) { PostgreSQLAdapter } + let(:connection) { active_record_pg_connection } + end + + end + end +end + diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/shared_fast_truncation.rb b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/shared_fast_truncation.rb new file mode 100644 index 0000000..e0cdb52 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation/shared_fast_truncation.rb @@ -0,0 +1,40 @@ +shared_examples_for "an adapter with pre-count truncation" do + describe "#pre_count_truncate_tables" do + + context "with :reset_ids set true" do + it "truncates the table" do + 2.times { User.create } + + connection.pre_count_truncate_tables(%w[users], :reset_ids => true) + User.count.should be_zero + end + + it "resets AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.pre_count_truncate_tables(%w[users]) # true is also the default + User.create.id.should == 1 + end + end + + + context "with :reset_ids set false" do + it "truncates the table" do + 2.times { User.create } + + connection.pre_count_truncate_tables(%w[users], :reset_ids => false) + User.count.should be_zero + end + + it "does not reset AUTO_INCREMENT index of table" do + 2.times { User.create } + User.delete_all + + connection.pre_count_truncate_tables(%w[users], :reset_ids => false) + + User.create.id.should == 3 + end + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation_spec.rb new file mode 100644 index 0000000..ee80b52 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/active_record/truncation_spec.rb @@ -0,0 +1,145 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'active_record' + +require 'database_cleaner/active_record/truncation' + +module ActiveRecord + module ConnectionAdapters + [MysqlAdapter, Mysql2Adapter, SQLite3Adapter, JdbcAdapter, PostgreSQLAdapter, IBM_DBAdapter].each do |adapter| + describe adapter, "#truncate_table" do + it "responds" do + adapter.instance_methods.should include('truncate_table') + end + end + end + end +end + +module DatabaseCleaner + module ActiveRecord + + describe Truncation do + let(:connection) { mock('connection') } + + before(:each) do + connection.stub!(:disable_referential_integrity).and_yield + connection.stub!(:database_cleaner_view_cache).and_return([]) + ::ActiveRecord::Base.stub!(:connection).and_return(connection) + end + + describe '#clean' do + it "should truncate all tables except for schema_migrations" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[schema_migrations widgets dogs]) + + connection.should_receive(:truncate_tables).with(['widgets', 'dogs']) + Truncation.new.clean + end + + it "should only truncate the tables specified in the :only option when provided" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[schema_migrations widgets dogs]) + + connection.should_receive(:truncate_tables).with(['widgets']) + + Truncation.new(:only => ['widgets']).clean + end + + it "should not truncate the tables specified in the :except option" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[schema_migrations widgets dogs]) + + connection.should_receive(:truncate_tables).with(['dogs']) + + Truncation.new(:except => ['widgets']).clean + end + + it "should raise an error when :only and :except options are used" do + running { + Truncation.new(:except => ['widgets'], :only => ['widgets']) + }.should raise_error(ArgumentError) + end + + it "should raise an error when invalid options are provided" do + running { Truncation.new(:foo => 'bar') }.should raise_error(ArgumentError) + end + + it "should not truncate views" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[widgets dogs]) + connection.stub!(:database_cleaner_view_cache).and_return(["widgets"]) + + connection.should_receive(:truncate_tables).with(['dogs']) + + Truncation.new.clean + end + + describe "relying on #pre_count_truncate_tables if connection allows it" do + subject { Truncation.new } + + it "should rely on #pre_count_truncate_tables if #pre_count? returns true" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[widgets dogs]) + connection.stub!(:database_cleaner_view_cache).and_return(["widgets"]) + + subject.instance_variable_set(:"@pre_count", true) + + connection.should_not_receive(:truncate_tables).with(['dogs']) + connection.should_receive(:pre_count_truncate_tables).with(['dogs'], :reset_ids => true) + + subject.clean + end + + it "should not rely on #pre_count_truncate_tables if #pre_count? return false" do + connection.stub!(:database_cleaner_table_cache).and_return(%w[widgets dogs]) + connection.stub!(:database_cleaner_view_cache).and_return(["widgets"]) + + subject.instance_variable_set(:"@pre_count", false) + + connection.should_not_receive(:pre_count_truncate_tables).with(['dogs'], :reset_ids => true) + connection.should_receive(:truncate_tables).with(['dogs']) + + subject.clean + end + end + end + + describe '#pre_count?' do + before(:each) do + connection.stub!(:disable_referential_integrity).and_yield + connection.stub!(:database_cleaner_view_cache).and_return([]) + ::ActiveRecord::Base.stub!(:connection).and_return(connection) + end + + subject { Truncation.new } + its(:pre_count?) { should == false } + + it 'should return true if @reset_id is set and non false or nil' do + subject.instance_variable_set(:"@pre_count", true) + subject.send(:pre_count?).should == true + end + + it 'should return false if @reset_id is set to false' do + subject.instance_variable_set(:"@pre_count", false) + subject.send(:pre_count?).should == false + end + end + + describe '#reset_ids?' do + before(:each) do + connection.stub!(:disable_referential_integrity).and_yield + connection.stub!(:database_cleaner_view_cache).and_return([]) + ::ActiveRecord::Base.stub!(:connection).and_return(connection) + end + + subject { Truncation.new } + its(:reset_ids?) { should == true } + + it 'should return true if @reset_id is set and non false or nil' do + subject.instance_variable_set(:"@reset_ids", 'Something') + subject.send(:reset_ids?).should == true + end + + it 'should return false if @reset_id is set to false' do + subject.instance_variable_set(:"@reset_ids", false) + subject.send(:reset_ids?).should == false + end + end + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/base_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/base_spec.rb new file mode 100644 index 0000000..58dd2a7 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/base_spec.rb @@ -0,0 +1,493 @@ +require File.dirname(__FILE__) + '/../spec_helper' +require 'database_cleaner/active_record/transaction' +require 'database_cleaner/data_mapper/transaction' +require 'database_cleaner/mongo_mapper/truncation' +require 'database_cleaner/mongoid/truncation' +require 'database_cleaner/couch_potato/truncation' + +module DatabaseCleaner + describe Base do + + describe "autodetect" do + + #Cache all ORMs, we'll need them later but not now. + before(:all) do + Temp_AR = ::ActiveRecord if defined?(::ActiveRecord) and not defined?(Temp_AR) + Temp_DM = ::DataMapper if defined?(::DataMapper) and not defined?(Temp_DM) + Temp_MM = ::MongoMapper if defined?(::MongoMapper) and not defined?(Temp_MM) + Temp_MO = ::Mongoid if defined?(::Mongoid) and not defined?(Temp_MO) + Temp_CP = ::CouchPotato if defined?(::CouchPotato) and not defined?(Temp_CP) + Temp_SQ = ::Sequel if defined?(::Sequel) and not defined?(Temp_SQ) + end + + #Remove all ORM mocks and restore from cache + after(:all) do + Object.send(:remove_const, 'ActiveRecord') if defined?(::ActiveRecord) + Object.send(:remove_const, 'DataMapper') if defined?(::DataMapper) + Object.send(:remove_const, 'MongoMapper') if defined?(::MongoMapper) + Object.send(:remove_const, 'Mongoid') if defined?(::Mongoid) + Object.send(:remove_const, 'CouchPotato') if defined?(::CouchPotato) + Object.send(:remove_const, 'Sequel') if defined?(::Sequel) + + + # Restore ORMs + ::ActiveRecord = Temp_AR if defined? Temp_AR + ::DataMapper = Temp_DM if defined? Temp_DM + ::MongoMapper = Temp_MM if defined? Temp_MM + ::Mongoid = Temp_MO if defined? Temp_MO + ::CouchPotato = Temp_CP if defined? Temp_CP + end + + #reset the orm mocks + before(:each) do + Object.send(:remove_const, 'ActiveRecord') if defined?(::ActiveRecord) + Object.send(:remove_const, 'DataMapper') if defined?(::DataMapper) + Object.send(:remove_const, 'MongoMapper') if defined?(::MongoMapper) + Object.send(:remove_const, 'Mongoid') if defined?(::Mongoid) + Object.send(:remove_const, 'CouchPotato') if defined?(::CouchPotato) + Object.send(:remove_const, 'Sequel') if defined?(::Sequel) + end + + let(:cleaner) { DatabaseCleaner::Base.new :autodetect } + + it "should raise an error when no ORM is detected" do + running { cleaner }.should raise_error(DatabaseCleaner::NoORMDetected) + end + + it "should detect ActiveRecord first" do + Object.const_set('ActiveRecord','Actively mocking records.') + Object.const_set('DataMapper', 'Mapping data mocks') + Object.const_set('MongoMapper', 'Mapping mock mongos') + Object.const_set('Mongoid', 'Mongoid mock') + Object.const_set('CouchPotato', 'Couching mock potatos') + Object.const_set('Sequel', 'Sequel mock') + + cleaner.orm.should == :active_record + cleaner.should be_auto_detected + end + + it "should detect DataMapper second" do + Object.const_set('DataMapper', 'Mapping data mocks') + Object.const_set('MongoMapper', 'Mapping mock mongos') + Object.const_set('Mongoid', 'Mongoid mock') + Object.const_set('CouchPotato', 'Couching mock potatos') + Object.const_set('Sequel', 'Sequel mock') + + cleaner.orm.should == :data_mapper + cleaner.should be_auto_detected + end + + it "should detect MongoMapper third" do + Object.const_set('MongoMapper', 'Mapping mock mongos') + Object.const_set('Mongoid', 'Mongoid mock') + Object.const_set('CouchPotato', 'Couching mock potatos') + Object.const_set('Sequel', 'Sequel mock') + + cleaner.orm.should == :mongo_mapper + cleaner.should be_auto_detected + end + + it "should detect Mongoid fourth" do + Object.const_set('Mongoid', 'Mongoid mock') + Object.const_set('CouchPotato', 'Couching mock potatos') + Object.const_set('Sequel', 'Sequel mock') + + cleaner.orm.should == :mongoid + cleaner.should be_auto_detected + end + + it "should detect CouchPotato fifth" do + Object.const_set('CouchPotato', 'Couching mock potatos') + Object.const_set('Sequel', 'Sequel mock') + + cleaner.orm.should == :couch_potato + cleaner.should be_auto_detected + end + + it "should detect Sequel last" do + Object.const_set('Sequel', 'Sequel mock') + + cleaner.orm.should == :sequel + cleaner.should be_auto_detected + end + end + + describe "orm_module" do + it "should ask ::DatabaseCleaner what the module is for its orm" do + orm = mock("orm") + mockule = mock("module") + + cleaner = ::DatabaseCleaner::Base.new + cleaner.should_receive(:orm).and_return(orm) + + ::DatabaseCleaner.should_receive(:orm_module).with(orm).and_return(mockule) + + cleaner.send(:orm_module).should == mockule + end + end + + describe "comparison" do + it "should be equal if orm, connection and strategy are the same" do + strategy = mock("strategy") + + one = DatabaseCleaner::Base.new(:active_record,:connection => :default) + one.strategy = strategy + + two = DatabaseCleaner::Base.new(:active_record,:connection => :default) + two.strategy = strategy + + one.should == two + two.should == one + end + end + + describe "initialization" do + context "db specified" do + subject { ::DatabaseCleaner::Base.new(:active_record,:connection => :my_db) } + + it "should store db from :connection in params hash" do + subject.db.should == :my_db + end + end + + describe "orm" do + it "should store orm" do + cleaner = ::DatabaseCleaner::Base.new :a_orm + cleaner.orm.should == :a_orm + end + + it "converts string to symbols" do + cleaner = ::DatabaseCleaner::Base.new "mongoid" + cleaner.orm.should == :mongoid + end + + it "is autodetected if orm is not provided" do + cleaner = ::DatabaseCleaner::Base.new + cleaner.should be_auto_detected + end + + it "is autodetected if you specify :autodetect" do + cleaner = ::DatabaseCleaner::Base.new "autodetect" + cleaner.should be_auto_detected + end + + it "should default to autodetect upon initalisation" do + subject.should be_auto_detected + end + end + end + + describe "db" do + it "should default to :default" do + subject.db.should == :default + end + + it "should return any stored db value" do + subject.stub(:strategy_db=) + subject.db = :test_db + subject.db.should == :test_db + end + + it "should pass db to any specified strategy" do + subject.should_receive(:strategy_db=).with(:a_new_db) + subject.db = :a_new_db + end + end + + describe "strategy_db=" do + let(:strategy) { mock("strategy") } + + before(:each) do + subject.strategy = strategy + end + + it "should check that strategy supports db specification" do + strategy.should_receive(:respond_to?).with(:db=).and_return(true) + strategy.stub(:db=) + subject.strategy_db = :a_db + end + + context "when strategy supports db specification" do + before(:each) { strategy.stub(:respond_to?).with(:db=).and_return true } + + it "should pass db to the strategy" do + strategy.should_receive(:db=).with(:a_db) + subject.strategy_db = :a_db + end + end + + context "when strategy doesn't supports db specification" do + before(:each) { strategy.stub(:respond_to?).with(:db=).and_return false } + + it "should check to see if db is :default" do + db = mock("default") + db.should_receive(:==).with(:default).and_return(true) + + subject.strategy_db = db + end + + it "should raise an argument error when db isn't default" do + db = mock("a db") + expect{ subject.strategy_db = db }.to raise_error ArgumentError + end + end + end + + describe "clean_with" do + let (:strategy) { mock("strategy",:clean => true) } + + before(:each) { subject.stub(:create_strategy).with(anything).and_return(strategy) } + + it "should pass all arguments to create_strategy" do + subject.should_receive(:create_strategy).with(:lorum, :dollar, :amet, :ipsum => "random").and_return(strategy) + subject.clean_with :lorum, :dollar, :amet, { :ipsum => "random" } + end + + it "should invoke clean on the created strategy" do + strategy.should_receive(:clean) + subject.clean_with :strategy + end + + it "should return the strategy" do + subject.clean_with( :strategy ).should == strategy + end + end + + describe "clean_with!" do + let (:strategy) { mock("strategy",:clean => true) } + + before(:each) { subject.stub(:create_strategy).with(anything).and_return(strategy) } + + it "should pass all arguments to create_strategy" do + subject.should_receive(:create_strategy).with(:lorum, :dollar, :amet, :ipsum => "random").and_return(strategy) + subject.clean_with! :lorum, :dollar, :amet, { :ipsum => "random" } + end + + it "should invoke clean on the created strategy" do + strategy.should_receive(:clean) + subject.clean_with! :strategy + end + + it "should return the strategy" do + subject.clean_with!( :strategy ).should == strategy + end + end + + describe "create_strategy" do + let(:klass) { mock("klass",:new => mock("instance")) } + + before :each do + subject.stub(:orm_strategy).and_return(klass) + end + + it "should pass the first argument to orm_strategy" do + subject.should_receive(:orm_strategy).with(:strategy).and_return(Object) + subject.create_strategy :strategy + end + it "should pass the remainding argument to orm_strategy.new" do + klass.should_receive(:new).with(:params => {:lorum => "ipsum"}) + + subject.create_strategy :strategy, {:params => {:lorum => "ipsum"}} + end + it "should return the resulting strategy" do + subject.create_strategy( :strategy ).should == klass.new + end + end + + describe "strategy=" do + let(:mock_strategy) { mock("strategy") } + + it "should proxy symbolised strategies to create_strategy" do + subject.should_receive(:create_strategy).with(:symbol) + subject.strategy = :symbol + end + + it "should proxy params with symbolised strategies" do + subject.should_receive(:create_strategy).with(:symbol,:param => "one") + subject.strategy= :symbol, {:param => "one"} + end + + it "should accept strategy objects" do + expect{ subject.strategy = mock_strategy }.to_not raise_error + end + + it "should raise argument error when params given with strategy Object" do + expect{ subject.strategy = mock("object"), {:param => "one"} }.to raise_error ArgumentError + end + + it "should attempt to set strategy db" do + subject.stub(:db).and_return(:my_db) + subject.should_receive(:strategy_db=).with(:my_db) + subject.strategy = mock_strategy + end + + it "should return the stored strategy" do + result = subject.strategy = mock_strategy + result.should == mock_strategy + end + end + + describe "strategy" do + subject { ::DatabaseCleaner::Base.new :a_orm } + + it "returns a null strategy when strategy no set and undetectable" do + subject.instance_values["@strategy"] = nil + subject.strategy.should == DatabaseCleaner::NullStrategy + end + + it "returns the set strategy" do + strategum = mock("strategy") + subject.strategy = strategum + subject.strategy.should == strategum + end + end + + describe "orm=" do + it "should stored the desired orm" do + subject.orm.should_not == :desired_orm + subject.orm = :desired_orm + subject.orm.should == :desired_orm + end + end + + describe "orm" do + let(:mock_orm) { mock("orm") } + + it "should return orm if orm set" do + subject.instance_variable_set "@orm", mock_orm + subject.orm.should == mock_orm + end + + context "orm isn't set" do + before(:each) { subject.instance_variable_set "@orm", nil } + + it "should run autodetect if orm isn't set" do + subject.should_receive(:autodetect) + subject.orm + end + + it "should return the result of autodetect if orm isn't set" do + subject.stub(:autodetect).and_return(mock_orm) + subject.orm.should == mock_orm + end + end + end + + describe "proxy methods" do + let (:strategy) { mock("strategy") } + + before(:each) do + subject.stub(:strategy).and_return(strategy) + end + + describe "start" do + it "should proxy start to the strategy" do + strategy.should_receive(:start) + subject.start + end + end + + describe "clean" do + it "should proxy clean to the strategy" do + strategy.should_receive(:clean) + subject.clean + end + end + + describe "clean!" do + it "should proxy clean! to the strategy clean" do + strategy.should_receive(:clean) + subject.clean! + end + end + end + + describe "auto_detected?" do + it "should return true unless @autodetected is nil" do + subject.instance_variable_set("@autodetected","not nil") + subject.auto_detected?.should be_true + end + + it "should return false if @autodetect is nil" do + subject.instance_variable_set("@autodetected",nil) + subject.auto_detected?.should be_false + end + end + + describe "orm_strategy" do + let (:klass) { mock("klass") } + + before(:each) do + subject.stub(:orm_module).and_return(klass) + end + + context "in response to a LoadError" do + before(:each) { subject.should_receive(:require).with(anything).and_raise(LoadError) } + + it "should catch LoadErrors" do + expect { subject.send(:orm_strategy,:a_strategy) }.to_not raise_error LoadError + end + + it "should raise UnknownStrategySpecified" do + expect { subject.send(:orm_strategy,:a_strategy) }.to raise_error UnknownStrategySpecified + end + + it "should ask orm_module if it will list available_strategies" do + klass.should_receive(:respond_to?).with(:available_strategies) + + subject.stub(:orm_module).and_return(klass) + + expect { subject.send(:orm_strategy,:a_strategy) }.to raise_error UnknownStrategySpecified + end + + it "should use available_strategies (for the error message) if its available" do + klass.stub(:respond_to?).with(:available_strategies).and_return(true) + klass.should_receive(:available_strategies).and_return([]) + + subject.stub(:orm_module).and_return(klass) + + expect { subject.send(:orm_strategy,:a_strategy) }.to raise_error UnknownStrategySpecified + end + end + + it "should return the constant of the Strategy class requested" do + strategy_klass = mock("strategy klass") + + subject.stub(:require).with(anything).and_return(true) + + klass.should_receive(:const_get).with("Cunningplan").and_return(strategy_klass) + + subject.send(:orm_strategy, :cunningplan).should == strategy_klass + end + + end + + describe 'set_default_orm_strategy' do + it 'sets strategy to :transaction for ActiveRecord' do + cleaner = DatabaseCleaner::Base.new(:active_record) + cleaner.strategy.should be_instance_of DatabaseCleaner::ActiveRecord::Transaction + end + + it 'sets strategy to :transaction for DataMapper' do + cleaner = DatabaseCleaner::Base.new(:data_mapper) + cleaner.strategy.should be_instance_of DatabaseCleaner::DataMapper::Transaction + end + + it 'sets strategy to :truncation for MongoMapper' do + cleaner = DatabaseCleaner::Base.new(:mongo_mapper) + cleaner.strategy.should be_instance_of DatabaseCleaner::MongoMapper::Truncation + end + + it 'sets strategy to :truncation for Mongoid' do + cleaner = DatabaseCleaner::Base.new(:mongoid) + cleaner.strategy.should be_instance_of DatabaseCleaner::Mongoid::Truncation + end + + it 'sets strategy to :truncation for CouchPotato' do + cleaner = DatabaseCleaner::Base.new(:couch_potato) + cleaner.strategy.should be_instance_of DatabaseCleaner::CouchPotato::Truncation + end + end + + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/configuration_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/configuration_spec.rb new file mode 100644 index 0000000..f05c5f5 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/configuration_spec.rb @@ -0,0 +1,294 @@ +require 'spec_helper' + +module DatabaseCleaner + class << self + def reset + @connections = nil + end + + def connections_stub!(array) + @connections = array + end + end +end + +describe ::DatabaseCleaner do + before(:each) { ::DatabaseCleaner.reset } + + context "orm specification" do + it "should not accept unrecognised orms" do + expect { ::DatabaseCleaner[nil] }.to raise_error(::DatabaseCleaner::NoORMDetected) + end + + it "should accept :active_record" do + cleaner = ::DatabaseCleaner[:active_record] + cleaner.should be_a(::DatabaseCleaner::Base) + cleaner.orm.should == :active_record + ::DatabaseCleaner.connections.size.should == 1 + end + + it "should accept :data_mapper" do + cleaner = ::DatabaseCleaner[:data_mapper] + cleaner.should be_a(::DatabaseCleaner::Base) + cleaner.orm.should == :data_mapper + ::DatabaseCleaner.connections.size.should == 1 + end + + it "should accept :mongo_mapper" do + cleaner = ::DatabaseCleaner[:mongo_mapper] + cleaner.should be_a(::DatabaseCleaner::Base) + cleaner.orm.should == :mongo_mapper + ::DatabaseCleaner.connections.size.should == 1 + end + + it "should accept :couch_potato" do + cleaner = ::DatabaseCleaner[:couch_potato] + cleaner.should be_a(::DatabaseCleaner::Base) + cleaner.orm.should == :couch_potato + ::DatabaseCleaner.connections.size.should == 1 + end + end + + it "should accept multiple orm's" do + ::DatabaseCleaner[:couch_potato] + ::DatabaseCleaner[:data_mapper] + ::DatabaseCleaner.connections.size.should == 2 + ::DatabaseCleaner.connections[0].orm.should == :couch_potato + ::DatabaseCleaner.connections[1].orm.should == :data_mapper + end + + context "connection/db specification" do + it "should accept a connection parameter and store it" do + cleaner = ::DatabaseCleaner[:active_record, {:connection => :first_connection}] + cleaner.should be_a(::DatabaseCleaner::Base) + cleaner.orm.should == :active_record + cleaner.db.should == :first_connection + end + + it "should accept multiple connections for a single orm" do + ::DatabaseCleaner[:data_mapper,{:connection => :first_db}] + ::DatabaseCleaner[:data_mapper,{:connection => :second_db}] + ::DatabaseCleaner.connections.size.should == 2 + ::DatabaseCleaner.connections[0].orm.should == :data_mapper + ::DatabaseCleaner.connections[0].db.should == :first_db + ::DatabaseCleaner.connections[1].orm.should == :data_mapper + ::DatabaseCleaner.connections[1].db.should == :second_db + end + + it "should accept multiple connections and multiple orms" do + ::DatabaseCleaner[:data_mapper, {:connection => :first_db} ] + ::DatabaseCleaner[:active_record,{:connection => :second_db}] + ::DatabaseCleaner[:active_record,{:connection => :first_db} ] + ::DatabaseCleaner[:data_mapper, {:connection => :second_db}] + + ::DatabaseCleaner.connections.size.should == 4 + + ::DatabaseCleaner.connections[0].orm.should == :data_mapper + ::DatabaseCleaner.connections[0].db.should == :first_db + + ::DatabaseCleaner.connections[1].orm.should == :active_record + ::DatabaseCleaner.connections[1].db.should == :second_db + + ::DatabaseCleaner.connections[2].orm.should == :active_record + ::DatabaseCleaner.connections[2].db.should == :first_db + + ::DatabaseCleaner.connections[3].orm.should == :data_mapper + ::DatabaseCleaner.connections[3].db.should == :second_db + end + end + + context "connection/db retrieval" do + it "should retrieve a db rather than create a new one" do + pending + connection = ::DatabaseCleaner[:active_record].strategy = :truncation + ::DatabaseCleaner[:active_record].should == connection + end + end + + context "class methods" do + subject { ::DatabaseCleaner } + + its(:connections) { should respond_to(:each) } + + it "should give me a default (autodetection) databasecleaner by default" do + cleaner = mock("cleaner").as_null_object + ::DatabaseCleaner::Base.should_receive(:new).with().and_return(cleaner) + + ::DatabaseCleaner.connections.should have(1).items + ::DatabaseCleaner.connections.first.should == cleaner + end + end + + context "single orm single connection" do + let(:connection) { ::DatabaseCleaner.connections.first } + + it "should proxy strategy=" do + stratagum = mock("stratagum") + connection.should_receive(:strategy=).with(stratagum) + ::DatabaseCleaner.strategy = stratagum + end + + it "should proxy orm=" do + orm = mock("orm") + connection.should_receive(:orm=).with(orm) + ::DatabaseCleaner.orm = orm + end + + it "should proxy start" do + connection.should_receive(:start) + ::DatabaseCleaner.start + end + + it "should proxy clean" do + connection.should_receive(:clean) + ::DatabaseCleaner.clean + end + + it "should proxy clean_with" do + stratagem = mock("stratgem") + connection.should_receive(:clean_with).with(stratagem, {}) + ::DatabaseCleaner.clean_with stratagem, {} + end + end + + context "multiple connections" do + + #these are relativly simple, all we need to do is make sure all connections are cleaned/started/cleaned_with appropriatly. + context "simple proxy methods" do + + let(:active_record) { mock("active_mock") } + let(:data_mapper) { mock("data_mock") } + + before(:each) do + ::DatabaseCleaner.stub!(:connections).and_return([active_record,data_mapper]) + end + + it "should proxy orm to all connections" do + active_record.should_receive(:orm=) + data_mapper.should_receive(:orm=) + + ::DatabaseCleaner.orm = mock("orm") + end + + it "should proxy start to all connections" do + active_record.should_receive(:start) + data_mapper.should_receive(:start) + + ::DatabaseCleaner.start + end + + it "should proxy clean to all connections" do + active_record.should_receive(:clean) + data_mapper.should_receive(:clean) + + ::DatabaseCleaner.clean + end + + it "should proxy clean_with to all connections" do + stratagem = mock("stratgem") + active_record.should_receive(:clean_with).with(stratagem) + data_mapper.should_receive(:clean_with).with(stratagem) + + ::DatabaseCleaner.clean_with stratagem + end + end + + # ah now we have some difficulty, we mustn't allow duplicate connections to exist, but they could + # plausably want to force orm/strategy change on two sets of orm that differ only on db + context "multiple orm proxy methods" do + + pending "should proxy orm to all connections and remove duplicate connections" do + active_record_1 = mock("active_mock_on_db_one").as_null_object + active_record_2 = mock("active_mock_on_db_two").as_null_object + data_mapper_1 = mock("data_mock_on_db_one").as_null_object + + ::DatabaseCleaner.connections_stub! [active_record_1,active_record_2,data_mapper_1] + + active_record_1.should_receive(:orm=).with(:data_mapper) + active_record_2.should_receive(:orm=).with(:data_mapper) + data_mapper_1.should_receive(:orm=).with(:data_mapper) + + active_record_1.should_receive(:==).with(data_mapper_1).and_return(true) + + ::DatabaseCleaner.connections.size.should == 3 + ::DatabaseCleaner.orm = :data_mapper + ::DatabaseCleaner.connections.size.should == 2 + end + + it "should proxy strategy to all connections and remove duplicate connections" do + active_record_1 = mock("active_mock_strategy_one").as_null_object + active_record_2 = mock("active_mock_strategy_two").as_null_object + strategy = mock("strategy") + + ::DatabaseCleaner.connections_stub! [active_record_1,active_record_2] + + active_record_1.should_receive(:strategy=).with(strategy) + active_record_2.should_receive(:strategy=).with(strategy) + + active_record_1.should_receive(:==).with(active_record_2).and_return(true) + + ::DatabaseCleaner.connections.size.should == 2 + ::DatabaseCleaner.strategy = strategy + ::DatabaseCleaner.connections.size.should == 1 + end + end + end + + describe "remove_duplicates" do + it "should remove duplicates if they are identical" do + orm = mock("orm") + connection = mock("a datamapper connection", :orm => orm ) + + ::DatabaseCleaner.connections_stub! [connection,connection,connection] + + ::DatabaseCleaner.remove_duplicates + ::DatabaseCleaner.connections.size.should == 1 + end + end + + describe "app_root" do + it "should default to Dir.pwd" do + DatabaseCleaner.app_root.should == Dir.pwd + end + + it "should store specific paths" do + DatabaseCleaner.app_root = '/path/to' + DatabaseCleaner.app_root.should == '/path/to' + end + end + + describe "orm_module" do + subject { ::DatabaseCleaner } + + it "should return DatabaseCleaner::ActiveRecord for :active_record" do + ::DatabaseCleaner::ActiveRecord = mock("ar module") unless defined? ::DatabaseCleaner::ActiveRecord + subject.orm_module(:active_record).should == DatabaseCleaner::ActiveRecord + end + + it "should return DatabaseCleaner::DataMapper for :data_mapper" do + ::DatabaseCleaner::DataMapper = mock("dm module") unless defined? ::DatabaseCleaner::DataMapper + subject.orm_module(:data_mapper).should == DatabaseCleaner::DataMapper + end + + it "should return DatabaseCleaner::MongoMapper for :mongo_mapper" do + ::DatabaseCleaner::MongoMapper = mock("mm module") unless defined? ::DatabaseCleaner::MongoMapper + subject.orm_module(:mongo_mapper).should == DatabaseCleaner::MongoMapper + end + + it "should return DatabaseCleaner::Mongoid for :mongoid" do + ::DatabaseCleaner::Mongoid = mock("mongoid module") unless defined? ::DatabaseCleaner::Mongoid + subject.orm_module(:mongoid).should == DatabaseCleaner::Mongoid + end + + it "should return DatabaseCleaner::Mongo for :mongo" do + ::DatabaseCleaner::Mongo = mock("mongo module") unless defined? ::DatabaseCleaner::Mongo + subject.orm_module(:mongo).should == DatabaseCleaner::Mongo + end + + it "should return DatabaseCleaner::CouchPotato for :couch_potato" do + ::DatabaseCleaner::CouchPotato = mock("cp module") unless defined? ::DatabaseCleaner::CouchPotato + subject.orm_module(:couch_potato).should == DatabaseCleaner::CouchPotato + end + + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/couch_potato/truncation_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/couch_potato/truncation_spec.rb new file mode 100644 index 0000000..542e5e1 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/couch_potato/truncation_spec.rb @@ -0,0 +1,41 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'database_cleaner/couch_potato/truncation' +require 'couch_potato' + +module DatabaseCleaner + module CouchPotato + + describe Truncation do + let(:database) { mock('database') } + + before(:each) do + ::CouchPotato.stub!(:couchrest_database).and_return(database) + end + + it "should re-create the database" do + database.should_receive(:recreate!) + + Truncation.new.clean + end + + it "should raise an error when the :only option is used" do + running { + Truncation.new(:only => ['document-type']) + }.should raise_error(ArgumentError) + end + + it "should raise an error when the :except option is used" do + running { + Truncation.new(:except => ['document-type']) + }.should raise_error(ArgumentError) + end + + it "should raise an error when invalid options are provided" do + running { + Truncation.new(:foo => 'bar') + }.should raise_error(ArgumentError) + end + end + + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/base_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/base_spec.rb new file mode 100644 index 0000000..6f57ddb --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/base_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' +require 'database_cleaner/data_mapper/base' +require 'database_cleaner/shared_strategy' + +module DatabaseCleaner + describe DataMapper do + it { should respond_to(:available_strategies) } + end + + module DataMapper + class ExampleStrategy + include ::DatabaseCleaner::DataMapper::Base + end + + describe ExampleStrategy do + it_should_behave_like "a generic strategy" + it { should respond_to(:db) } + it { should respond_to(:db=) } + + it "should store my desired db" do + subject.db = :my_db + subject.db.should == :my_db + end + + it "should default to :default" do + subject.db.should == :default + end + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/transaction_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/transaction_spec.rb new file mode 100644 index 0000000..e4c1a97 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/transaction_spec.rb @@ -0,0 +1,23 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'database_cleaner/data_mapper/transaction' +require 'database_cleaner/shared_strategy' +#require 'data_mapper' + +module DatabaseCleaner + module DataMapper + + describe Transaction do + it_should_behave_like "a generic strategy" + it_should_behave_like "a generic transaction strategy" + + describe "start" do + it "should start a transaction" + end + + describe "clean" do + it "should finish a transaction" + end + end + + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/truncation_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/truncation_spec.rb new file mode 100644 index 0000000..7e3c11d --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/data_mapper/truncation_spec.rb @@ -0,0 +1,11 @@ +require 'database_cleaner/data_mapper/truncation' +require 'database_cleaner/shared_strategy' + +module DatabaseCleaner + module DataMapper + describe Truncation do + it_should_behave_like "a generic strategy" + it_should_behave_like "a generic truncation strategy" + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/generic/base_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/generic/base_spec.rb new file mode 100644 index 0000000..ef3ee1e --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/generic/base_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' +require 'database_cleaner/shared_strategy' +require 'database_cleaner/generic/base' + +module ::DatabaseCleaner + module Generic + class ExampleStrategy + include ::DatabaseCleaner::Generic::Base + end + + describe ExampleStrategy do + context "class methods" do + subject { ExampleStrategy } + its(:available_strategies) { should be_empty } + end + + it_should_behave_like "a generic strategy" + + its(:db) { should == :default } + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/generic/truncation_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/generic/truncation_spec.rb new file mode 100644 index 0000000..ef5a5c5 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/generic/truncation_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' +require 'database_cleaner/generic/truncation' + +module ::DatabaseCleaner + module Generic + class TruncationExample + include ::DatabaseCleaner::Generic::Truncation + + def only + @only + end + + def except + @tables_to_exclude + end + + def reset_ids? + !!@reset_ids + end + + def pre_count? + !!@pre_count + end + end + + class MigrationExample < TruncationExample + def migration_storage_name + "migration_storage_name" + end + end + + describe TruncationExample do + its(:start) { expect{ subject }.to_not raise_error } + its(:clean) { expect{ subject }.to raise_error(NotImplementedError) } + + context "private methods" do + it { should_not respond_to(:tables_to_truncate) } + its(:tables_to_truncate) { expect{ subject }.to raise_error(NotImplementedError) } + + it { should_not respond_to(:migration_storage_name) } + its(:migration_storage_name) { should be_nil } + end + + describe "initialize" do + it { expect{ subject }.to_not raise_error } + + it "should accept a hash of options" do + expect{ TruncationExample.new {} }.to_not raise_error + end + + it { expect{ TruncationExample.new( { :a_random_param => "should raise ArgumentError" } ) }.to raise_error(ArgumentError) } + it { expect{ TruncationExample.new( { :except => "something",:only => "something else" } ) }.to raise_error(ArgumentError) } + it { expect{ TruncationExample.new( { :only => "something" } ) }.to_not raise_error(ArgumentError) } + it { expect{ TruncationExample.new( { :except => "something" } ) }.to_not raise_error(ArgumentError) } + it { expect{ TruncationExample.new( { :pre_count => "something" } ) }.to_not raise_error(ArgumentError) } + it { expect{ TruncationExample.new( { :reset_ids => "something" } ) }.to_not raise_error(ArgumentError) } + + context "" do + subject { TruncationExample.new( { :only => ["something"] } ) } + its(:only) { should == ["something"] } + its(:except) { should == [] } + end + + context "" do + subject { TruncationExample.new( { :except => ["something"] } ) } + its(:only) { should == nil } + its(:except) { should include("something") } + end + + context "" do + subject { TruncationExample.new( { :reset_ids => ["something"] } ) } + its(:reset_ids?) { should == true } + end + + context "" do + subject { TruncationExample.new( { :reset_ids => nil } ) } + its(:reset_ids?) { should == false } + end + + context "" do + subject { TruncationExample.new( { :pre_count => ["something"] } ) } + its(:pre_count?) { should == true } + end + + context "" do + subject { TruncationExample.new( { :pre_count => nil } ) } + its(:pre_count?) { should == false } + end + + context "" do + subject { MigrationExample.new } + its(:only) { should == nil } + its(:except) { should == ["migration_storage_name"] } + end + + context "" do + EXCEPT_TABLES = ["something"] + subject { MigrationExample.new( { :except => EXCEPT_TABLES } ) } + + it "should not modify the array of excepted tables" do + subject.except.should include("migration_storage_name") + EXCEPT_TABLES.should_not include("migration_storage_name") + end + end + end + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/base_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/base_spec.rb new file mode 100644 index 0000000..11c0f05 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/base_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require 'database_cleaner/mongo_mapper/base' +require 'database_cleaner/shared_strategy' + +module DatabaseCleaner + describe MongoMapper do + it { should respond_to(:available_strategies) } + end + + module MongoMapper + class ExampleStrategy + include ::DatabaseCleaner::MongoMapper::Base + end + + describe ExampleStrategy do + + it_should_behave_like "a generic strategy" + + describe "db" do + it { should respond_to(:db=) } + + it "should store my desired db" do + subject.db = :my_db + subject.db.should == :my_db + end + + it "should default to :default" do + subject.db.should == :default + end + end + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/mongo_examples.rb b/vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/mongo_examples.rb new file mode 100644 index 0000000..44637ea --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/mongo_examples.rb @@ -0,0 +1,8 @@ +class Widget + include ::MongoMapper::Document + key :name, String +end +class Gadget + include ::MongoMapper::Document + key :name, String +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/truncation_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/truncation_spec.rb new file mode 100644 index 0000000..59e3116 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/mongo_mapper/truncation_spec.rb @@ -0,0 +1,74 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mongo_mapper' +require 'database_cleaner/mongo_mapper/truncation' +require File.dirname(__FILE__) + '/mongo_examples' + +module DatabaseCleaner + module MongoMapper + + describe Truncation do + + #doing this in the file root breaks autospec, doing it before(:all) just fails the specs + before(:all) do + ::MongoMapper.connection = ::Mongo::Connection.new('127.0.0.1') + @test_db = 'database_cleaner_specs' + ::MongoMapper.database = @test_db + end + + before(:each) do + ::MongoMapper.connection.drop_database(@test_db) + end + + def ensure_counts(expected_counts) + # I had to add this sanity_check garbage because I was getting non-determinisc results from mongomapper at times.. + # very odd and disconcerting... + sanity_check = expected_counts.delete(:sanity_check) + begin + expected_counts.each do |model_class, expected_count| + model_class.count.should equal(expected_count), "#{model_class} expected to have a count of #{expected_count} but was #{model_class.count}" + end + rescue Spec::Expectations::ExpectationNotMetError => e + raise !sanity_check ? e : Spec::ExpectationNotMetError::ExpectationNotMetError.new("SANITY CHECK FAILURE! This should never happen here: #{e.message}") + end + end + + def create_widget(attrs={}) + Widget.new({:name => 'some widget'}.merge(attrs)).save! + end + + def create_gadget(attrs={}) + Gadget.new({:name => 'some gadget'}.merge(attrs)).save! + end + + it "truncates all collections by default" do + create_widget + create_gadget + ensure_counts(Widget => 1, Gadget => 1, :sanity_check => true) + Truncation.new.clean + ensure_counts(Widget => 0, Gadget => 0) + end + + context "when collections are provided to the :only option" do + it "only truncates the specified collections" do + create_widget + create_gadget + ensure_counts(Widget => 1, Gadget => 1, :sanity_check => true) + Truncation.new(:only => ['widgets']).clean + ensure_counts(Widget => 0, Gadget => 1) + end + end + + context "when collections are provided to the :except option" do + it "truncates all but the specified collections" do + create_widget + create_gadget + ensure_counts(Widget => 1, Gadget => 1, :sanity_check => true) + Truncation.new(:except => ['widgets']).clean + ensure_counts(Widget => 1, Gadget => 0) + end + end + + end + + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/sequel/base_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/sequel/base_spec.rb new file mode 100644 index 0000000..9d3da56 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/sequel/base_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' +require 'database_cleaner/sequel/base' +require 'database_cleaner/shared_strategy' +require 'sequel' + +module DatabaseCleaner + describe Sequel do + it { should respond_to(:available_strategies) } + end + + module Sequel + class ExampleStrategy + include ::DatabaseCleaner::Sequel::Base + end + + describe ExampleStrategy do + it_should_behave_like "a generic strategy" + it { should respond_to(:db) } + it { should respond_to(:db=) } + + it "should store my desired db" do + subject.db = :my_db + subject.db.should == :my_db + end + + it "should default to :default" do + pending "I figure out how to use Sequel and write some real tests for it..." + subject.db.should == :default + end + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/sequel/transaction_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/sequel/transaction_spec.rb new file mode 100644 index 0000000..794481e --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/sequel/transaction_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' +require 'database_cleaner/sequel/transaction' +require 'database_cleaner/shared_strategy' +require 'sequel' + +module DatabaseCleaner + module Sequel + describe Transaction do + it_should_behave_like "a generic strategy" + it_should_behave_like "a generic transaction strategy" + + describe "start" do + it "should start a transaction" + end + + describe "clean" do + it "should finish a transaction" + end + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/sequel/truncation_spec.rb b/vendor/gems/database_cleaner/spec/database_cleaner/sequel/truncation_spec.rb new file mode 100644 index 0000000..1552265 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/sequel/truncation_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' +require 'database_cleaner/sequel/truncation' +require 'database_cleaner/shared_strategy' +require 'sequel' + +module DatabaseCleaner + module Sequel + describe Truncation do + it_should_behave_like "a generic strategy" + it_should_behave_like "a generic truncation strategy" + end + end +end diff --git a/vendor/gems/database_cleaner/spec/database_cleaner/shared_strategy.rb b/vendor/gems/database_cleaner/spec/database_cleaner/shared_strategy.rb new file mode 100644 index 0000000..ee219fa --- /dev/null +++ b/vendor/gems/database_cleaner/spec/database_cleaner/shared_strategy.rb @@ -0,0 +1,13 @@ +shared_examples_for "a generic strategy" do + it { should respond_to(:db) } +end + +shared_examples_for "a generic truncation strategy" do + it { should respond_to(:start) } + it { should respond_to(:clean) } +end + +shared_examples_for "a generic transaction strategy" do + it { should respond_to(:start) } + it { should respond_to(:clean) } +end diff --git a/vendor/gems/database_cleaner/spec/rcov.opts b/vendor/gems/database_cleaner/spec/rcov.opts new file mode 100644 index 0000000..263b944 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/rcov.opts @@ -0,0 +1 @@ +--exclude "spec/*,vendor/*,examples/*,features/*,Users/*/.rvm/gems/*/gems/*" \ No newline at end of file diff --git a/vendor/gems/database_cleaner/spec/spec_helper.rb b/vendor/gems/database_cleaner/spec/spec_helper.rb new file mode 100644 index 0000000..80d0a99 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/spec_helper.rb @@ -0,0 +1,21 @@ +require "rubygems" + +require "bundler" +Bundler.setup + +require 'rspec/core' +require 'rspec/mocks' + +#require 'active_record' +#require 'mongo_mapper' + +$:.unshift(File.dirname(__FILE__)) +$:.unshift(File.dirname(__FILE__) + '/../lib') + +require 'database_cleaner' + +RSpec.configure do |config| + +end + +alias running lambda diff --git a/vendor/gems/database_cleaner/spec/support/active_record/database_setup.rb b/vendor/gems/database_cleaner/spec/support/active_record/database_setup.rb new file mode 100644 index 0000000..0e358b0 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/support/active_record/database_setup.rb @@ -0,0 +1,4 @@ +def db_config + config_path = 'db/config.yml' + @db_config ||= YAML.load(IO.read(config_path)) +end diff --git a/vendor/gems/database_cleaner/spec/support/active_record/mysql2_setup.rb b/vendor/gems/database_cleaner/spec/support/active_record/mysql2_setup.rb new file mode 100644 index 0000000..86391d6 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/support/active_record/mysql2_setup.rb @@ -0,0 +1,38 @@ +require 'support/active_record/database_setup' +require 'support/active_record/schema_setup' + +module MySQL2Helper + puts "Active Record #{ActiveRecord::VERSION::STRING}, mysql2" + + # require 'logger' + # ActiveRecord::Base.logger = Logger.new(STDERR) + + def config + db_config['mysql2'] + end + + def create_db + establish_connection(config.merge(:database => nil)) + + ActiveRecord::Base.connection.drop_database config['database'] rescue nil + ActiveRecord::Base.connection.create_database config['database'] + end + + def establish_connection config = config + ActiveRecord::Base.establish_connection config + end + + def active_record_mysql2_setup + create_db + establish_connection + load_schema + end + + def active_record_mysql2_connection + ActiveRecord::Base.connection + end +end + +RSpec.configure do |c| + c.include MySQL2Helper +end diff --git a/vendor/gems/database_cleaner/spec/support/active_record/mysql_setup.rb b/vendor/gems/database_cleaner/spec/support/active_record/mysql_setup.rb new file mode 100644 index 0000000..6f9df85 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/support/active_record/mysql_setup.rb @@ -0,0 +1,38 @@ +require 'support/active_record/database_setup' +require 'support/active_record/schema_setup' + +module MySQLHelper + puts "Active Record #{ActiveRecord::VERSION::STRING}, mysql" + + # require 'logger' + # ActiveRecord::Base.logger = Logger.new(STDERR) + + def config + db_config['mysql'] + end + + def create_db + establish_connection(config.merge(:database => nil)) + + ActiveRecord::Base.connection.drop_database config['database'] rescue nil + ActiveRecord::Base.connection.create_database config['database'] + end + + def establish_connection config = config + ActiveRecord::Base.establish_connection config + end + + def active_record_mysql_setup + create_db + establish_connection + load_schema + end + + def active_record_mysql_connection + ActiveRecord::Base.connection + end +end + +RSpec.configure do |c| + c.include MySQLHelper +end diff --git a/vendor/gems/database_cleaner/spec/support/active_record/postgresql_setup.rb b/vendor/gems/database_cleaner/spec/support/active_record/postgresql_setup.rb new file mode 100644 index 0000000..f606453 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/support/active_record/postgresql_setup.rb @@ -0,0 +1,41 @@ +require 'support/active_record/database_setup' +require 'support/active_record/schema_setup' + +module PostgreSQLHelper + puts "Active Record #{ActiveRecord::VERSION::STRING}, pg" + + # ActiveRecord::Base.logger = Logger.new(STDERR) + + def config + db_config['postgres'] + end + + def create_db + @encoding = config['encoding'] || ENV['CHARSET'] || 'utf8' + begin + establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) + ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding)) + rescue Exception => e + $stderr.puts e, *(e.backtrace) + $stderr.puts "Couldn't create database for #{config.inspect}" + end + end + + def establish_connection config = config + ActiveRecord::Base.establish_connection(config) + end + + def active_record_pg_setup + create_db + establish_connection + load_schema + end + + def active_record_pg_connection + ActiveRecord::Base.connection + end +end + +RSpec.configure do |c| + c.include PostgreSQLHelper +end diff --git a/vendor/gems/database_cleaner/spec/support/active_record/schema_setup.rb b/vendor/gems/database_cleaner/spec/support/active_record/schema_setup.rb new file mode 100644 index 0000000..ff02304 --- /dev/null +++ b/vendor/gems/database_cleaner/spec/support/active_record/schema_setup.rb @@ -0,0 +1,10 @@ +def load_schema + ActiveRecord::Schema.define do + create_table :users, :force => true do |t| + t.integer :name + end + end +end + +class ::User < ActiveRecord::Base +end diff --git a/vendor/gems/orm_adapter/.gitignore b/vendor/gems/orm_adapter/.gitignore new file mode 100644 index 0000000..35d6389 --- /dev/null +++ b/vendor/gems/orm_adapter/.gitignore @@ -0,0 +1,15 @@ +# system crap +.DS_Store + +# local ruby/gems dev stuff +.rvmrc +.bundle +Gemfile.lock + +# built docs +.yardoc +doc + +# built gems +pkg +*.gem \ No newline at end of file diff --git a/vendor/gems/orm_adapter/.rspec b/vendor/gems/orm_adapter/.rspec new file mode 100644 index 0000000..53607ea --- /dev/null +++ b/vendor/gems/orm_adapter/.rspec @@ -0,0 +1 @@ +--colour diff --git a/vendor/gems/orm_adapter/.travis.yml b/vendor/gems/orm_adapter/.travis.yml new file mode 100644 index 0000000..5a7c4e3 --- /dev/null +++ b/vendor/gems/orm_adapter/.travis.yml @@ -0,0 +1,4 @@ +language: ruby +rvm: + - 1.9.3 +before_install: "cp Gemfile.lock.development Gemfile.lock" \ No newline at end of file diff --git a/vendor/gems/orm_adapter/Gemfile b/vendor/gems/orm_adapter/Gemfile new file mode 100644 index 0000000..857c670 --- /dev/null +++ b/vendor/gems/orm_adapter/Gemfile @@ -0,0 +1,3 @@ +source "http://rubygems.org" + +gemspec \ No newline at end of file diff --git a/vendor/gems/orm_adapter/Gemfile.lock.development b/vendor/gems/orm_adapter/Gemfile.lock.development new file mode 100644 index 0000000..5ba2fb5 --- /dev/null +++ b/vendor/gems/orm_adapter/Gemfile.lock.development @@ -0,0 +1,129 @@ +PATH + remote: . + specs: + orm_adapter (0.4.0) + +GEM + remote: http://rubygems.org/ + specs: + activemodel (3.1.3) + activesupport (= 3.1.3) + builder (~> 3.0.0) + i18n (~> 0.6) + activerecord (3.1.3) + activemodel (= 3.1.3) + activesupport (= 3.1.3) + arel (~> 2.2.1) + tzinfo (~> 0.3.29) + activesupport (3.1.3) + multi_json (~> 1.0) + addressable (2.2.6) + arel (2.2.1) + bcrypt-ruby (3.0.1) + bson (1.5.2) + bson_ext (1.5.2) + bson (= 1.5.2) + builder (3.0.0) + data_objects (0.10.7) + addressable (~> 2.1) + datamapper (1.2.0) + dm-aggregates (~> 1.2.0) + dm-constraints (~> 1.2.0) + dm-core (~> 1.2.0) + dm-migrations (~> 1.2.0) + dm-serializer (~> 1.2.0) + dm-timestamps (~> 1.2.0) + dm-transactions (~> 1.2.0) + dm-types (~> 1.2.0) + dm-validations (~> 1.2.0) + diff-lcs (1.1.3) + dm-active_model (1.2.0) + activemodel (~> 3.1.0) + dm-core (~> 1.2.0) + dm-aggregates (1.2.0) + dm-core (~> 1.2.0) + dm-constraints (1.2.0) + dm-core (~> 1.2.0) + dm-core (1.2.0) + addressable (~> 2.2.6) + dm-do-adapter (1.2.0) + data_objects (~> 0.10.6) + dm-core (~> 1.2.0) + dm-migrations (1.2.0) + dm-core (~> 1.2.0) + dm-serializer (1.2.1) + dm-core (~> 1.2.0) + fastercsv (~> 1.5.4) + json (~> 1.6.1) + json_pure (~> 1.6.1) + multi_json (~> 1.0.3) + dm-sqlite-adapter (1.2.0) + dm-do-adapter (~> 1.2.0) + do_sqlite3 (~> 0.10.6) + dm-timestamps (1.2.0) + dm-core (~> 1.2.0) + dm-transactions (1.2.0) + dm-core (~> 1.2.0) + dm-types (1.2.1) + bcrypt-ruby (~> 3.0.0) + dm-core (~> 1.2.0) + fastercsv (~> 1.5.4) + json (~> 1.6.1) + multi_json (~> 1.0.3) + stringex (~> 1.3.0) + uuidtools (~> 2.1.2) + dm-validations (1.2.0) + dm-core (~> 1.2.0) + do_sqlite3 (0.10.7) + data_objects (= 0.10.7) + fastercsv (1.5.4) + git (1.2.5) + i18n (0.6.0) + json (1.6.4) + json_pure (1.6.4) + mongo (1.5.2) + bson (= 1.5.2) + mongo_mapper (0.10.1) + activemodel (~> 3.0) + activesupport (~> 3.0) + plucky (~> 0.4.0) + mongoid (2.4.0) + activemodel (~> 3.1) + mongo (~> 1.3) + tzinfo (~> 0.3.22) + multi_json (1.0.4) + plucky (0.4.3) + mongo (~> 1.3) + rake (0.9.2.2) + rspec (2.8.0) + rspec-core (~> 2.8.0) + rspec-expectations (~> 2.8.0) + rspec-mocks (~> 2.8.0) + rspec-core (2.8.0) + rspec-expectations (2.8.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.8.0) + sqlite3 (1.3.5) + stringex (1.3.0) + tzinfo (0.3.31) + uuidtools (2.1.2) + yard (0.7.4) + +PLATFORMS + ruby + +DEPENDENCIES + activerecord (>= 3.0.0) + bson_ext (>= 1.3.0) + bundler (>= 1.0.0) + datamapper (>= 1.0) + dm-active_model (>= 1.0) + dm-sqlite-adapter (>= 1.0) + git (>= 1.2.5) + mongo_mapper (>= 0.9.0) + mongoid (>= 2.0.0.beta.20) + orm_adapter! + rake (>= 0.8.7) + rspec (>= 2.4.0) + sqlite3 (>= 1.3.2) + yard (>= 0.6.0) diff --git a/vendor/gems/orm_adapter/History.txt b/vendor/gems/orm_adapter/History.txt new file mode 100644 index 0000000..cb3046b --- /dev/null +++ b/vendor/gems/orm_adapter/History.txt @@ -0,0 +1,58 @@ +== 0.4.0 + + * Adds :limit, and :offset options to #find_all [Fred Wu] + +== 0.3.0 + + * Removes OrmAdapter::Base.model_classes and friends. This is a BC breaking change if you use .model_classes [Ian White] + * Add note about the scope of ORM Adapter re: model instance methods + +== 0.2.0 + + * Normalise :id in find conditions [Tim Galeckas, Ian White] + * Allow find_first and find_all to take no arguments [Tim Galeckas, Ian White] + +== 0.1.0 + + * Add #destroy(object) to the API [Fred Wu] + + +== 0.0.7 + + * Lazy load Active Record [José Valim] + + +== 0.0.6 + + * Active Record 3.1 association compatibility [Ian White] + * Compatibility with mongoid master wrt. OrmAdapter::Base#get [Frank Wöckener]. Closes #9 [Ian White] + + +== 0.0.5 + + * Adds Mongo Mapper adapter [Luke Cunningham] + + +== 0.0.4 + + * Adds ability to order results from #find_first and #find_all [Ian White] + * Rationalise development dependencies and release process [Ian White] + + +== 0.0.3 + + * ActiveRecord's OrmAdapter handles non standard foreign key names [Ian White] + * fix ruby 1.9.2 problems. [Ian White] + - removes "can't add a new key into hash during iteration" problem with ActiveRecord OrmAdapter + - removes problem with rspec 2 mocks interacting with ruby 1.9.2 #to_ary + + +== 0.0.2 + + * Add #get to the API. Ensure both #get and #get! complies with to_key requirements. [José Valim] + * Extract tests into shared example. Give instructions on how to write a new adapter. [Ian White] + + +== 0.0.1 + + * Initial release [Ian White, José Valim] \ No newline at end of file diff --git a/vendor/gems/orm_adapter/LICENSE b/vendor/gems/orm_adapter/LICENSE new file mode 100644 index 0000000..eb68551 --- /dev/null +++ b/vendor/gems/orm_adapter/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010-2012 Ian White and José Valim + +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. diff --git a/vendor/gems/orm_adapter/README.rdoc b/vendor/gems/orm_adapter/README.rdoc new file mode 100644 index 0000000..5110ad5 --- /dev/null +++ b/vendor/gems/orm_adapter/README.rdoc @@ -0,0 +1,71 @@ += ORM Adapter {Build Status}[http://travis-ci.org/ianwhite/orm_adapter] + +Provides a single point of entry for popular ruby ORMs. Its target audience is gem authors who want to support more than one ORM. + +== Example of use + + require 'orm_adapter' + + User # is it an ActiveRecord, DM Resource, MongoMapper or MongoId Document? + + User.to_adapter.find_first :name => 'Fred' # we don't care! + + user_model = User.to_adapter + user_model.get!(1) # find a record by id + user_model.find_first(:name => 'fred') # find first fred + user_model.find_first(:level => 'awesome', :id => 23) + # find user 23, only if it's level is awesome + user_model.find_all # find all users + user_model.find_all(:name => 'fred') # find all freds + user_model.find_all(:order => :name) # find all freds, ordered by name + user_model.create!(:name => 'fred') # create a fred + user_model.destroy(object) # destroy the user object + + +@see OrmAdapter::Base for more details of the supported API + +== Supported ORMs + +Currently supported ORMs are *ActiveRecord*, *DataMapper*, *MongoMapper*, and *MongoId*. + +We welcome you to write new adapters as gems. ORM Adapter will stay focused in having these major ORMs working. + +To write an adapter look at lib/orm_adapter/adapters/active_record.rb for an example of implementation. To see how to test it, look at spec/orm_adapter/example_app_shared.rb, spec/orm_adapter/adapters/active_record_spec.rb. You'll need to require the target ORM in spec/spec_helper.rb + + +== Goals + +ORM Adapter's goal is to support a minimum API used by most of the plugins that needs agnosticism beyond Active Model. + +ORM Adapter will support only basic methods, as +get+, +find_first+, create! and so forth. It is not ORM Adapter's goal to support different query constructions, handle table joins, etc. + +ORM adapter provides a consistent API for these basic class or 'factory' methods. It does not attempt to unify the behaviour of model instances returned by these methods. This means that unifying the behaviour of methods such as `model.save`, and `model.valid?` is beyond the scope of orm_adapter. + +If you need complex queries, we recommend you to subclass ORM Adapters in your plugin and extend it expressing these query conditions as part of your domain logic. + +== History + +orm_adapter is an extraction from {pickle}[http://github.com/ianwhite/pickle] by {Ian White}[http://github.com/ianwhite]. Pickle's orm adapter included work by {Daniel Neighman}[http://github.com/hassox], {Josh Bassett}[http://github.com/nullobject], {Marc Lee}[http://github.com/maleko], and {Sebastian Zuchmanski}[http://github.com/sebcioz]. + +{José Valim}[http://github.com/josevalim] suggested the extraction, and worked on the first release with Ian. + +{Luke Cunningham}[http://github.com/icaruswings] contributes the Mongo Mapper adapter. + +{Fred Wu}[http://github.com/fredwu] contributes the #destroy methods, and :limit, :offset options for #find_all + +{Tim Galeckas}[http://github.com/timgaleckas] + +== Development + +To run the specs, you can start from the last known good set of gem dependencies in Gemfile.lock.development: + + git clone http://github.com/ianwhite/orm_adapter + cd orm_adapter + cp Gemfile.lock.development Gemfile.lock + bundle + bundle exec rake spec + + +== Copyright + +Copyright (c) 2010-2012 Ian White and José Valim. See LICENSE for details. diff --git a/vendor/gems/orm_adapter/Rakefile b/vendor/gems/orm_adapter/Rakefile new file mode 100644 index 0000000..f9704ef --- /dev/null +++ b/vendor/gems/orm_adapter/Rakefile @@ -0,0 +1,37 @@ +#!/usr/bin/env rake +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +require 'rake' +require 'rspec/core/rake_task' +$:.push File.expand_path("../lib", __FILE__) +require "orm_adapter/version" + +task :default => :spec + +RSpec::Core::RakeTask.new(:spec) + +begin + require 'yard' + YARD::Rake::YardocTask.new(:doc) do |t| + t.files = ['lib/**/*.rb', 'README.rdoc'] + end +rescue LoadError + task :doc do + puts "install yard to generate the docs" + end +end + +Bundler::GemHelper.install_tasks + +task :release => :check_gemfile + +task :check_gemfile do + if File.exists?("Gemfile.lock") && File.read("Gemfile.lock") != File.read("Gemfile.lock.development") + cp "Gemfile.lock", "Gemfile.lock.development" + raise "** Gemfile.lock.development has been updated, please commit these changes." + end +end \ No newline at end of file diff --git a/vendor/gems/orm_adapter/lib/orm_adapter.rb b/vendor/gems/orm_adapter/lib/orm_adapter.rb new file mode 100644 index 0000000..5178fa0 --- /dev/null +++ b/vendor/gems/orm_adapter/lib/orm_adapter.rb @@ -0,0 +1,15 @@ +require 'orm_adapter/base' +require 'orm_adapter/to_adapter' +require 'orm_adapter/version' + +module OrmAdapter + # A collection of registered adapters + def self.adapters + @@adapters ||= [] + end +end + +require 'orm_adapter/adapters/active_record' if defined?(ActiveRecord::Base) +require 'orm_adapter/adapters/data_mapper' if defined?(DataMapper::Resource) +require 'orm_adapter/adapters/mongoid' if defined?(Mongoid::Document) +require 'orm_adapter/adapters/mongo_mapper' if defined?(MongoMapper::Document) \ No newline at end of file diff --git a/vendor/gems/orm_adapter/lib/orm_adapter/adapters/active_record.rb b/vendor/gems/orm_adapter/lib/orm_adapter/adapters/active_record.rb new file mode 100644 index 0000000..7f13492 --- /dev/null +++ b/vendor/gems/orm_adapter/lib/orm_adapter/adapters/active_record.rb @@ -0,0 +1,76 @@ +require 'active_record' +require 'active_record/model' + +module OrmAdapter + class ActiveRecord < Base + # Return list of column/property names + def column_names + klass.column_names + end + + # @see OrmAdapter::Base#get! + def get!(id) + klass.find(wrap_key(id)) + end + + # @see OrmAdapter::Base#get + def get(id) + klass.where(klass.primary_key => wrap_key(id)).first + end + + # @see OrmAdapter::Base#find_first + def find_first(options = {}) + conditions, order = extract_conditions!(options) + klass.where(conditions_to_fields(conditions)).order(*order_clause(order)).first + end + + # @see OrmAdapter::Base#find_all + def find_all(options = {}) + conditions, order, limit, offset = extract_conditions!(options) + klass.where(conditions_to_fields(conditions)).order(*order_clause(order)).limit(limit).offset(offset).all + end + + # @see OrmAdapter::Base#create! + def create!(attributes = {}) + klass.create!(attributes) + end + + # @see OrmAdapter::Base#destroy + def destroy(object) + object.destroy && true if valid_object?(object) + end + + protected + + # Introspects the klass to convert and objects in conditions into foreign key and type fields + def conditions_to_fields(conditions) + fields = {} + conditions.each do |key, value| + if value.is_a?(::ActiveRecord::Base) && (assoc = klass.reflect_on_association(key.to_sym)) && assoc.belongs_to? + + if ::ActiveRecord::VERSION::STRING < "3.1" + fields[assoc.primary_key_name] = value.send(value.class.primary_key) + fields[assoc.options[:foreign_type]] = value.class.base_class.name.to_s if assoc.options[:polymorphic] + else # >= 3.1 + fields[assoc.foreign_key] = value.send(value.class.primary_key) + fields[assoc.foreign_type] = value.class.base_class.name.to_s if assoc.options[:polymorphic] + end + + else + fields[key] = value + end + end + fields + end + + def order_clause(order) + order.map {|pair| "#{pair[0]} #{pair[1]}"}.join(",") + end + end +end + +ActiveSupport.on_load(:active_record) do + ActiveRecord::Model.send :include, ::OrmAdapter::ToAdapter + ActiveRecord::Base.send :include, ::OrmAdapter::ToAdapter + ActiveRecord::Base::OrmAdapter = ::OrmAdapter::ActiveRecord +end diff --git a/vendor/gems/orm_adapter/lib/orm_adapter/adapters/data_mapper.rb b/vendor/gems/orm_adapter/lib/orm_adapter/adapters/data_mapper.rb new file mode 100644 index 0000000..8fb1530 --- /dev/null +++ b/vendor/gems/orm_adapter/lib/orm_adapter/adapters/data_mapper.rb @@ -0,0 +1,57 @@ +require 'dm-core' + +module DataMapper + module Model + include OrmAdapter::ToAdapter + end + + module Resource + class OrmAdapter < ::OrmAdapter::Base + # get a list of column names for a given class + def column_names + klass.properties.map(&:name) + end + + # @see OrmAdapter::Base#get! + def get!(id) + klass.get!(id) + end + + # @see OrmAdapter::Base#get + def get(id) + klass.get(id) + end + + # @see OrmAdapter::Base#find_first + def find_first(options = {}) + conditions, order = extract_conditions!(options) + klass.first :conditions => conditions, :order => order_clause(order) + end + + # @see OrmAdapter::Base#find_all + def find_all(options = {}) + conditions, order, limit, offset = extract_conditions!(options) + opts = { :conditions => conditions, :order => order_clause(order) } + opts = opts.merge({ :limit => limit }) unless limit.nil? + opts = opts.merge({ :offset => offset }) unless offset.nil? + klass.all opts + end + + # @see OrmAdapter::Base#create! + def create!(attributes = {}) + klass.create(attributes) + end + + # @see OrmAdapter::Base#destroy + def destroy(object) + object.destroy if valid_object?(object) + end + + protected + + def order_clause(order) + order.map {|pair| pair.first.send(pair.last)} + end + end + end +end diff --git a/vendor/gems/orm_adapter/lib/orm_adapter/adapters/mongo_mapper.rb b/vendor/gems/orm_adapter/lib/orm_adapter/adapters/mongo_mapper.rb new file mode 100644 index 0000000..2937d3c --- /dev/null +++ b/vendor/gems/orm_adapter/lib/orm_adapter/adapters/mongo_mapper.rb @@ -0,0 +1,65 @@ +require 'mongo_mapper' + +module MongoMapper + module Document + module ClassMethods + include OrmAdapter::ToAdapter + end + + class OrmAdapter < ::OrmAdapter::Base + # get a list of column names for a given class + def column_names + klass.column_names + end + + # @see OrmAdapter::Base#get! + def get!(id) + klass.find!(wrap_key(id)) + end + + # @see OrmAdapter::Base#get + def get(id) + klass.first({ :id => wrap_key(id) }) + end + + # @see OrmAdapter::Base#find_first + def find_first(conditions = {}) + conditions, order = extract_conditions!(conditions) + conditions = conditions.merge(:sort => order) unless order.nil? + klass.first(conditions_to_fields(conditions)) + end + + # @see OrmAdapter::Base#find_all + def find_all(conditions = {}) + conditions, order, limit, offset = extract_conditions!(conditions) + conditions = conditions.merge(:sort => order) unless order.nil? + conditions = conditions.merge(:limit => limit) unless limit.nil? + conditions = conditions.merge(:offset => offset) unless limit.nil? || offset.nil? + klass.all(conditions_to_fields(conditions)) + end + + # @see OrmAdapter::Base#create! + def create!(attributes = {}) + klass.create!(attributes) + end + + # @see OrmAdapter::Base#destroy + def destroy(object) + object.destroy if valid_object?(object) + end + + protected + + # converts and documents to ids + def conditions_to_fields(conditions) + conditions.inject({}) do |fields, (key, value)| + if value.is_a?(MongoMapper::Document) && klass.key?("#{key}_id") + fields.merge("#{key}_id" => value.id) + else + fields.merge(key => value) + end + end + end + end + end +end diff --git a/vendor/gems/orm_adapter/lib/orm_adapter/adapters/mongoid.rb b/vendor/gems/orm_adapter/lib/orm_adapter/adapters/mongoid.rb new file mode 100644 index 0000000..aaca494 --- /dev/null +++ b/vendor/gems/orm_adapter/lib/orm_adapter/adapters/mongoid.rb @@ -0,0 +1,63 @@ +require 'mongoid' + +module Mongoid + module Document + module ClassMethods + include OrmAdapter::ToAdapter + end + + class OrmAdapter < ::OrmAdapter::Base + # get a list of column names for a given class + def column_names + klass.fields.keys + end + + # @see OrmAdapter::Base#get! + def get!(id) + klass.find(wrap_key(id)) + end + + # @see OrmAdapter::Base#get + def get(id) + klass.where(:_id => wrap_key(id)).first + end + + # @see OrmAdapter::Base#find_first + def find_first(options = {}) + conditions, order = extract_conditions!(options) + klass.limit(1).where(conditions_to_fields(conditions)).order_by(order).first + end + + # @see OrmAdapter::Base#find_all + def find_all(options = {}) + conditions, order, limit, offset = extract_conditions!(options) + klass.where(conditions_to_fields(conditions)).order_by(order).limit(limit).offset(offset) + end + + # @see OrmAdapter::Base#create! + def create!(attributes = {}) + klass.create!(attributes) + end + + # @see OrmAdapter::Base#destroy + def destroy(object) + object.destroy if valid_object?(object) + end + + protected + + # converts and documents to ids + def conditions_to_fields(conditions) + conditions.inject({}) do |fields, (key, value)| + if value.is_a?(Mongoid::Document) && klass.fields.keys.include?("#{key}_id") + fields.merge("#{key}_id" => value.id) + elsif key.to_s == 'id' + fields.merge('_id' => value) + else + fields.merge(key => value) + end + end + end + end + end +end diff --git a/vendor/gems/orm_adapter/lib/orm_adapter/base.rb b/vendor/gems/orm_adapter/lib/orm_adapter/base.rb new file mode 100644 index 0000000..1bb6133 --- /dev/null +++ b/vendor/gems/orm_adapter/lib/orm_adapter/base.rb @@ -0,0 +1,127 @@ +module OrmAdapter + class Base + attr_reader :klass + + # Your ORM adapter needs to inherit from this Base class and its adapter + # will be registered. To create an adapter you should create an inner + # constant "OrmAdapter" e.g. ActiveRecord::Base::OrmAdapter + # + # @see orm_adapters/active_record + # @see orm_adapters/datamapper + # @see orm_adapters/mongoid + def self.inherited(adapter) + OrmAdapter.adapters << adapter + super + end + + def initialize(klass) + @klass = klass + end + + # Get a list of column/property/field names + def column_names + raise NotSupportedError + end + + # Get an instance by id of the model. Raises an error if a model is not found. + # This should comply with ActiveModel#to_key API, i.e.: + # + # User.to_adapter.get!(@user.to_key) == @user + # + def get!(id) + raise NotSupportedError + end + + # Get an instance by id of the model. Returns nil if a model is not found. + # This should comply with ActiveModel#to_key API, i.e.: + # + # User.to_adapter.get(@user.to_key) == @user + # + def get(id) + raise NotSupportedError + end + + # Find the first instance, optionally matching conditions, and specifying order + # + # You can call with just conditions, providing a hash + # + # User.to_adapter.find_first :name => "Fred", :age => 23 + # + # Or you can specify :order, and :conditions as keys + # + # User.to_adapter.find_first :conditions => {:name => "Fred", :age => 23} + # User.to_adapter.find_first :order => [:age, :desc] + # User.to_adapter.find_first :order => :name, :conditions => {:age => 18} + # + # When specifying :order, it may be + # * a single arg e.g. :order => :name + # * a single pair with :asc, or :desc as last, e.g. :order => [:name, :desc] + # * an array of single args or pairs (with :asc or :desc as last), e.g. :order => [[:name, :asc], [:age, :desc]] + # + def find_first(options = {}) + raise NotSupportedError + end + + # Find all models, optionally matching conditions, and specifying order + # @see OrmAdapter::Base#find_first for how to specify order and conditions + def find_all(options = {}) + raise NotSupportedError + end + + # Create a model using attributes + def create!(attributes = {}) + raise NotSupportedError + end + + # Destroy an instance by passing in the instance itself. + def destroy(object) + raise NotSupportedError + end + + protected + + def valid_object?(object) + object.class == klass + end + + def wrap_key(key) + key.is_a?(Array) ? key.first : key + end + + # given an options hash, + # with optional :conditions, :order, :limit and :offset keys, + # returns conditions, normalized order, limit and offset + def extract_conditions!(options = {}) + order = normalize_order(options.delete(:order)) + limit = options.delete(:limit) + offset = options.delete(:offset) + conditions = options.delete(:conditions) || options + + [conditions, order, limit, offset] + end + + # given an order argument, returns an array of pairs, with each pair containing the attribute, and :asc or :desc + def normalize_order(order) + order = Array(order) + + if order.length == 2 && !order[0].is_a?(Array) && [:asc, :desc].include?(order[1]) + order = [order] + else + order = order.map {|pair| pair.is_a?(Array) ? pair : [pair, :asc] } + end + + order.each do |pair| + pair.length == 2 or raise ArgumentError, "each order clause must be a pair (unknown clause #{pair.inspect})" + [:asc, :desc].include?(pair[1]) or raise ArgumentError, "order must be specified with :asc or :desc (unknown key #{pair[1].inspect})" + end + + order + end + end + + class NotSupportedError < NotImplementedError + def to_s + "method not supported by this orm adapter" + end + end +end diff --git a/vendor/gems/orm_adapter/lib/orm_adapter/to_adapter.rb b/vendor/gems/orm_adapter/lib/orm_adapter/to_adapter.rb new file mode 100644 index 0000000..82fca12 --- /dev/null +++ b/vendor/gems/orm_adapter/lib/orm_adapter/to_adapter.rb @@ -0,0 +1,14 @@ +require 'active_support/concern' + +module OrmAdapter + module ToAdapter + extend ActiveSupport::Concern + + included do + puts "INCLUDING!" + def self.to_adapter + @_to_adapter ||= self::OrmAdapter.new(self) + end + end + end +end diff --git a/vendor/gems/orm_adapter/lib/orm_adapter/version.rb b/vendor/gems/orm_adapter/lib/orm_adapter/version.rb new file mode 100644 index 0000000..db2a9b5 --- /dev/null +++ b/vendor/gems/orm_adapter/lib/orm_adapter/version.rb @@ -0,0 +1,3 @@ +module OrmAdapter + VERSION = "0.4.0" +end \ No newline at end of file diff --git a/vendor/gems/orm_adapter/orm_adapter.gemspec b/vendor/gems/orm_adapter/orm_adapter.gemspec new file mode 100644 index 0000000..26abeb3 --- /dev/null +++ b/vendor/gems/orm_adapter/orm_adapter.gemspec @@ -0,0 +1,35 @@ +$:.push File.expand_path("../lib", __FILE__) +require "orm_adapter/version" + +Gem::Specification.new do |s| + s.name = "orm_adapter" + s.version = OrmAdapter::VERSION.dup + s.platform = Gem::Platform::RUBY + s.authors = ["Ian White", "Jose Valim"] + s.description = "Provides a single point of entry for using basic features of ruby ORMs" + s.summary = "orm_adapter provides a single point of entry for using basic features of popular ruby ORMs. Its target audience is gem authors who want to support many ruby ORMs." + s.email = "ian.w.white@gmail.com" + s.homepage = "http://github.com/ianwhite/orm_adapter" + + s.rubyforge_project = "orm_adapter" + s.required_rubygems_version = ">= 1.3.6" + + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.require_paths = ["lib"] + + s.add_development_dependency "bundler", ">= 1.0.0" + s.add_development_dependency "git", ">= 1.2.5" + s.add_development_dependency "yard", ">= 0.6.0" + s.add_development_dependency "rake", ">= 0.8.7" + s.add_development_dependency "activerecord", ">= 3.0.0" + s.add_development_dependency "mongoid", ">= 2.0.0.beta.20" + s.add_development_dependency "mongo_mapper", ">= 0.9.0" + s.add_development_dependency "bson_ext", ">= 1.3.0" + s.add_development_dependency "rspec", ">= 2.4.0" + s.add_development_dependency "sqlite3", ">= 1.3.2" + s.add_development_dependency "datamapper", ">= 1.0" + s.add_development_dependency "dm-sqlite-adapter", ">= 1.0" + s.add_development_dependency "dm-active_model", ">= 1.0" +end + diff --git a/vendor/gems/orm_adapter/spec/orm_adapter/adapters/active_record_spec.rb b/vendor/gems/orm_adapter/spec/orm_adapter/adapters/active_record_spec.rb new file mode 100644 index 0000000..126beb0 --- /dev/null +++ b/vendor/gems/orm_adapter/spec/orm_adapter/adapters/active_record_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' +require 'orm_adapter/example_app_shared' + +if !defined?(ActiveRecord::Base) + puts "** require 'active_record' to run the specs in #{__FILE__}" +else + ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ":memory:") + + ActiveRecord::Migration.suppress_messages do + ActiveRecord::Schema.define(:version => 0) do + create_table(:users, :force => true) {|t| t.string :name; t.integer :rating; } + create_table(:notes, :force => true) {|t| t.belongs_to :owner, :polymorphic => true } + end + end + + module ArOrmSpec + class User < ActiveRecord::Base + has_many :notes, :as => :owner + end + + class AbstractNoteClass < ActiveRecord::Base + self.abstract_class = true + end + + class Note < AbstractNoteClass + belongs_to :owner, :polymorphic => true + end + + # here be the specs! + describe '[ActiveRecord orm adapter]' do + before do + User.delete_all + Note.delete_all + end + + it_should_behave_like "example app with orm_adapter" do + let(:user_class) { User } + let(:note_class) { Note } + end + + describe "#conditions_to_fields" do + describe "with non-standard association keys" do + class PerverseNote < Note + belongs_to :user, :foreign_key => 'owner_id' + belongs_to :pwner, :polymorphic => true, :foreign_key => 'owner_id', :foreign_type => 'owner_type' + end + + let(:user) { User.create! } + let(:adapter) { PerverseNote.to_adapter } + + it "should convert polymorphic object in conditions to the appropriate fields" do + adapter.send(:conditions_to_fields, :pwner => user).should == {'owner_id' => user.id, 'owner_type' => user.class.name} + end + + it "should convert belongs_to object in conditions to the appropriate fields" do + adapter.send(:conditions_to_fields, :user => user).should == {'owner_id' => user.id} + end + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/orm_adapter/spec/orm_adapter/adapters/data_mapper_spec.rb b/vendor/gems/orm_adapter/spec/orm_adapter/adapters/data_mapper_spec.rb new file mode 100644 index 0000000..ac01860 --- /dev/null +++ b/vendor/gems/orm_adapter/spec/orm_adapter/adapters/data_mapper_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' +require 'orm_adapter/example_app_shared' + +if !defined?(DataMapper) + puts "** require 'dm-core' to run the specs in #{__FILE__}" +else + + DataMapper.setup(:default, 'sqlite::memory:') + + module DmOrmSpec + class User + include DataMapper::Resource + property :id, Serial + property :name, String + property :rating, Integer + has n, :notes, :child_key => [:owner_id] + end + + class Note + include DataMapper::Resource + property :id, Serial + property :body, String + belongs_to :owner, 'User' + end + + require 'dm-migrations' + DataMapper.finalize + DataMapper.auto_migrate! + + # here be the specs! + describe DataMapper::Resource::OrmAdapter do + before do + User.destroy + Note.destroy + end + + it_should_behave_like "example app with orm_adapter" do + let(:user_class) { User } + let(:note_class) { Note } + + def reload_model(model) + model.class.get(model.id) + end + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/orm_adapter/spec/orm_adapter/adapters/mongo_mapper_spec.rb b/vendor/gems/orm_adapter/spec/orm_adapter/adapters/mongo_mapper_spec.rb new file mode 100644 index 0000000..c75f480 --- /dev/null +++ b/vendor/gems/orm_adapter/spec/orm_adapter/adapters/mongo_mapper_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' +require 'orm_adapter/example_app_shared' + +if !defined?(MongoMapper) || !(Mongo::Connection.new.db('orm_adapter_spec') rescue nil) + puts "** require 'mongo_mapper' and start mongod to run the specs in #{__FILE__}" +else + + MongoMapper.connection = Mongo::Connection.new + MongoMapper.database = "orm_adapter_spec" + + + module MongoMapperOrmSpec + class User + include MongoMapper::Document + key :name + key :rating + many :notes, :foreign_key => :owner_id, :class_name => 'MongoMapperOrmSpec::Note' + end + + class Note + include MongoMapper::Document + key :body, :default => "made by orm" + belongs_to :owner, :class_name => 'MongoMapperOrmSpec::User' + end + + # here be the specs! + describe MongoMapper::Document::OrmAdapter do + + before do + MongoMapper.database.collections.each do | coll | + coll.remove + end + end + + it_should_behave_like "example app with orm_adapter" do + let(:user_class) { User } + let(:note_class) { Note } + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/orm_adapter/spec/orm_adapter/adapters/mongoid_spec.rb b/vendor/gems/orm_adapter/spec/orm_adapter/adapters/mongoid_spec.rb new file mode 100644 index 0000000..d2ad526 --- /dev/null +++ b/vendor/gems/orm_adapter/spec/orm_adapter/adapters/mongoid_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' +require 'orm_adapter/example_app_shared' + +if !defined?(Mongoid) || !(Mongo::Connection.new.db('orm_adapter_spec') rescue nil) + puts "** require 'mongoid' and start mongod to run the specs in #{__FILE__}" +else + + Mongoid.configure do |config| + config.master = Mongo::Connection.new.db('orm_adapter_spec') + end + + module MongoidOrmSpec + class User + include Mongoid::Document + field :name + field :rating + has_many_related :notes, :foreign_key => :owner_id, :class_name => 'MongoidOrmSpec::Note' + end + + class Note + include Mongoid::Document + field :body, :default => "made by orm" + belongs_to_related :owner, :class_name => 'MongoidOrmSpec::User' + end + + # here be the specs! + describe Mongoid::Document::OrmAdapter do + before do + User.delete_all + Note.delete_all + end + + it_should_behave_like "example app with orm_adapter" do + let(:user_class) { User } + let(:note_class) { Note } + end + end + end +end \ No newline at end of file diff --git a/vendor/gems/orm_adapter/spec/orm_adapter/base_spec.rb b/vendor/gems/orm_adapter/spec/orm_adapter/base_spec.rb new file mode 100644 index 0000000..fe0a5a4 --- /dev/null +++ b/vendor/gems/orm_adapter/spec/orm_adapter/base_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +describe OrmAdapter::Base do + subject { OrmAdapter::Base.new(Object) } + + describe "#extract_conditions!" do + let(:conditions) { {:foo => 'bar'} } + let(:order) { [[:foo, :asc]] } + let(:limit) { 1 } + let(:offset) { 2 } + + it "()" do + subject.send(:extract_conditions!, conditions).should == [conditions, [], nil, nil] + end + + it "(:conditions => )" do + subject.send(:extract_conditions!, :conditions => conditions).should == [conditions, [], nil, nil] + end + + it "(:order => )" do + subject.send(:extract_conditions!, :order => order).should == [{}, order, nil, nil] + end + + it "(:limit => )" do + subject.send(:extract_conditions!, :limit => limit).should == [{}, [], limit, nil] + end + + it "(:offset => )" do + subject.send(:extract_conditions!, :offset => offset).should == [{}, [], nil, offset] + end + + it "(:conditions => , :order => )" do + subject.send(:extract_conditions!, :conditions => conditions, :order => order).should == [conditions, order, nil, nil] + end + + it "(:conditions => , :limit => )" do + subject.send(:extract_conditions!, :conditions => conditions, :limit => limit).should == [conditions, [], limit, nil] + end + + it "(:conditions => , :offset => )" do + subject.send(:extract_conditions!, :conditions => conditions, :offset => offset).should == [conditions, [], nil, offset] + end + + describe "#valid_object?" do + it "determines whether an object is valid for the current model class" do + subject.send(:valid_object?, Object.new).should be_true + subject.send(:valid_object?, String.new).should be_false + end + end + + describe "#normalize_order" do + specify "(nil) returns []" do + subject.send(:normalize_order, nil).should == [] + end + + specify ":foo returns [[:foo, :asc]]" do + subject.send(:normalize_order, :foo).should == [[:foo, :asc]] + end + + specify "[:foo] returns [[:foo, :asc]]" do + subject.send(:normalize_order, [:foo]).should == [[:foo, :asc]] + end + + specify "[:foo, :desc] returns [[:foo, :desc]]" do + subject.send(:normalize_order, [:foo, :desc]).should == [[:foo, :desc]] + end + + specify "[:foo, [:bar, :asc], [:baz, :desc], :bing] returns [[:foo, :asc], [:bar, :asc], [:baz, :desc], [:bing, :asc]]" do + subject.send(:normalize_order, [:foo, [:bar, :asc], [:baz, :desc], :bing]).should == [[:foo, :asc], [:bar, :asc], [:baz, :desc], [:bing, :asc]] + end + + specify "[[:foo, :wtf]] raises ArgumentError" do + lambda { subject.send(:normalize_order, [[:foo, :wtf]]) }.should raise_error(ArgumentError) + end + + specify "[[:foo, :asc, :desc]] raises ArgumentError" do + lambda { subject.send(:normalize_order, [[:foo, :asc, :desc]]) }.should raise_error(ArgumentError) + end + end + end +end diff --git a/vendor/gems/orm_adapter/spec/orm_adapter/example_app_shared.rb b/vendor/gems/orm_adapter/spec/orm_adapter/example_app_shared.rb new file mode 100644 index 0000000..a80dc31 --- /dev/null +++ b/vendor/gems/orm_adapter/spec/orm_adapter/example_app_shared.rb @@ -0,0 +1,240 @@ +# to test your new orm_adapter, make an example app that matches the functionality +# found in the existing specs for example, look at spec/orm_adapter/adapters/active_record_spec.rb +# +# Then you can execute this shared spec as follows: +# +# it_should_behave_like "execute app with orm_adapter" do +# let(:user_class) { User } +# let(:note_class) { Note } +# +# # optionaly define the following functions if the ORM does not support +# # this syntax - this should NOT use the orm_adapter, because we're testing that +# def create_model(klass, attrs = {}) +# klass.create!(attrs) +# end +# +# def reload_model(model) +# model.class.find(model.id) +# end +# end +# +shared_examples_for "example app with orm_adapter" do + + def create_model(klass, attrs = {}) + klass.create!(attrs) + end + + def reload_model(model) + model.class.find(model.id) + end + + describe "an ORM class" do + subject { note_class } + + it "#to_adapter should return an adapter instance" do + subject.to_adapter.should be_a(OrmAdapter::Base) + end + + it "#to_adapter should return an adapter for the receiver" do + subject.to_adapter.klass.should == subject + end + + it "#to_adapter should be cached" do + subject.to_adapter.object_id.should == subject.to_adapter.object_id + end + end + + describe "adapter instance" do + let(:note_adapter) { note_class.to_adapter } + let(:user_adapter) { user_class.to_adapter } + + describe "#get!(id)" do + it "should return the instance with id if it exists" do + user = create_model(user_class) + user_adapter.get!(user.id).should == user + end + + it "should allow to_key like arguments" do + user = create_model(user_class) + user_adapter.get!(user.to_key).should == user + end + + it "should raise an error if there is no instance with that id" do + lambda { user_adapter.get!("nonexistent id") }.should raise_error + end + end + + describe "#get(id)" do + it "should return the instance with id if it exists" do + user = create_model(user_class) + user_adapter.get(user.id).should == user + end + + it "should allow to_key like arguments" do + user = create_model(user_class) + user_adapter.get(user.to_key).should == user + end + + it "should return nil if there is no instance with that id" do + user_adapter.get("nonexistent id").should be_nil + end + end + + describe "#find_first" do + describe "(conditions)" do + it "should return first model matching conditions, if it exists" do + user = create_model(user_class, :name => "Fred") + user_adapter.find_first(:name => "Fred").should == user + end + + it "should return nil if no conditions match" do + user_adapter.find_first(:name => "Betty").should == nil + end + + it 'should return the first model if no conditions passed' do + user = create_model(user_class) + create_model(user_class) + user_adapter.find_first.should == user + end + + it "when conditions contain associated object, should return first model if it exists" do + user = create_model(user_class) + note = create_model(note_class, :owner => user) + note_adapter.find_first(:owner => user).should == note + end + + it "understands :id as a primary key condition (allowing scoped finding)" do + create_model(user_class, :name => "Fred") + user = create_model(user_class, :name => "Fred") + user_adapter.find_first(:id => user.id, :name => "Fred").should == user + user_adapter.find_first(:id => user.id, :name => "Not Fred").should be_nil + end + end + + describe "(:order => )" do + it "should return first model in specified order" do + user1 = create_model(user_class, :name => "Fred", :rating => 1) + user2 = create_model(user_class, :name => "Fred", :rating => 2) + user_adapter.find_first(:order => [:name, [:rating, :desc]]).should == user2 + end + end + + describe "(:conditions => , :order => )" do + it "should return first model matching conditions, in specified order" do + user1 = create_model(user_class, :name => "Fred", :rating => 1) + user2 = create_model(user_class, :name => "Fred", :rating => 2) + user_adapter.find_first(:conditions => {:name => "Fred"}, :order => [:rating, :desc]).should == user2 + end + end + end + + describe "#find_all" do + describe "(conditions)" do + it "should return only models matching conditions" do + user1 = create_model(user_class, :name => "Fred") + user2 = create_model(user_class, :name => "Fred") + user3 = create_model(user_class, :name => "Betty") + user_adapter.find_all(:name => "Fred").should == [user1, user2] + end + + it "should return all models if no conditions passed" do + user1 = create_model(user_class, :name => "Fred") + user2 = create_model(user_class, :name => "Fred") + user3 = create_model(user_class, :name => "Betty") + user_adapter.find_all.should == [user1, user2, user3] + end + + it "should return empty array if no conditions match" do + user_adapter.find_all(:name => "Fred").should == [] + end + + it "when conditions contain associated object, should return first model if it exists" do + user1, user2 = create_model(user_class), create_model(user_class) + note1 = create_model(note_class, :owner => user1) + note2 = create_model(note_class, :owner => user2) + note_adapter.find_all(:owner => user2).should == [note2] + end + end + + describe "(:order => )" do + it "should return all models in specified order" do + user1 = create_model(user_class, :name => "Fred", :rating => 1) + user2 = create_model(user_class, :name => "Fred", :rating => 2) + user3 = create_model(user_class, :name => "Betty", :rating => 1) + user_adapter.find_all(:order => [:name, [:rating, :desc]]).should == [user3, user2, user1] + end + end + + describe "(:conditions => , :order => )" do + it "should return only models matching conditions, in specified order" do + user1 = create_model(user_class, :name => "Fred", :rating => 1) + user2 = create_model(user_class, :name => "Fred", :rating => 2) + user3 = create_model(user_class, :name => "Betty", :rating => 1) + user_adapter.find_all(:conditions => {:name => "Fred"}, :order => [:rating, :desc]).should == [user2, user1] + end + end + + describe "(:limit => )" do + it "should return a limited set of matching models" do + user1 = create_model(user_class, :name => "Fred", :rating => 1) + user2 = create_model(user_class, :name => "Fred", :rating => 2) + user3 = create_model(user_class, :name => "Betty", :rating => 1) + user_adapter.find_all(:limit => 1).should == [user1] + user_adapter.find_all(:limit => 2).should == [user1, user2] + end + end + + describe "(:offset => ) with limit (as DataMapper doesn't allow offset on its own)" do + it "should return an offset set of matching models" do + user1 = create_model(user_class, :name => "Fred", :rating => 1) + user2 = create_model(user_class, :name => "Fred", :rating => 2) + user3 = create_model(user_class, :name => "Betty", :rating => 1) + user_adapter.find_all(:limit => 3, :offset => 0).should == [user1, user2, user3] + user_adapter.find_all(:limit => 3, :offset => 1).should == [user2, user3] + user_adapter.find_all(:limit => 1, :offset => 1).should == [user2] + end + end + end + + describe "#create!(attributes)" do + it "should create a model with the passed attributes" do + user = user_adapter.create!(:name => "Fred") + reload_model(user).name.should == "Fred" + end + + it "should raise error when create fails" do + lambda { user_adapter.create!(:user => create_model(note_class)) }.should raise_error + end + + it "when attributes contain an associated object, should create a model with the attributes" do + user = create_model(user_class) + note = note_adapter.create!(:owner => user) + reload_model(note).owner.should == user + end + + it "when attributes contain an has_many assoc, should create a model with the attributes" do + notes = [create_model(note_class), create_model(note_class)] + user = user_adapter.create!(:notes => notes) + reload_model(user).notes.should == notes + end + end + + describe "#destroy(instance)" do + it "should destroy the instance if it exists" do + user = create_model(user_class) + user_adapter.destroy(user).should == true + user_adapter.get(user.id).should be_nil + end + + it "should return nil if passed with an invalid instance" do + user_adapter.destroy("nonexistent instance").should be_nil + end + + it "should not destroy the instance if it doesn't match the model class" do + user = create_model(user_class) + note_adapter.destroy(user).should be_nil + user_adapter.get(user.id).should == user + end + end + end +end diff --git a/vendor/gems/orm_adapter/spec/orm_adapter_spec.rb b/vendor/gems/orm_adapter/spec/orm_adapter_spec.rb new file mode 100644 index 0000000..2a62516 --- /dev/null +++ b/vendor/gems/orm_adapter/spec/orm_adapter_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe OrmAdapter do + subject { OrmAdapter } + + describe "when a new adapter is created (by inheriting form OrmAdapter::Base)" do + let!(:adapter) { Class.new(OrmAdapter::Base) } + + its(:adapters) { should include(adapter) } + + after { OrmAdapter.adapters.delete(adapter) } + end +end diff --git a/vendor/gems/orm_adapter/spec/spec_helper.rb b/vendor/gems/orm_adapter/spec/spec_helper.rb new file mode 100644 index 0000000..36011ed --- /dev/null +++ b/vendor/gems/orm_adapter/spec/spec_helper.rb @@ -0,0 +1,15 @@ +require 'rubygems' +require 'rspec' + +$:.unshift(File.dirname(__FILE__) + '/../lib') + +['dm-core', 'mongoid', 'active_record', 'mongo_mapper'].each do |orm| + begin + require orm + rescue LoadError + puts "#{orm} not available" + end +end + +require 'dm-active_model' if defined?(DataMapper) +require 'orm_adapter' \ No newline at end of file diff --git a/vendor/gems/paperclip/.gitignore b/vendor/gems/paperclip/.gitignore new file mode 100644 index 0000000..740b83b --- /dev/null +++ b/vendor/gems/paperclip/.gitignore @@ -0,0 +1,24 @@ +*~ +*.swp +.rvmrc +.bundle +tmp +.DS_Store + +test/s3.yml +test/debug.log +test/paperclip.db +test/doc +test/pkg +test/tmp + +public +paperclip*.gem +capybara*.html + +*.rbc +.rbx + +*SPIKE* +*emfile.lock +tags diff --git a/vendor/gems/paperclip/.travis.yml b/vendor/gems/paperclip/.travis.yml new file mode 100644 index 0000000..3ed5675 --- /dev/null +++ b/vendor/gems/paperclip/.travis.yml @@ -0,0 +1,16 @@ +rvm: + - 1.9.2 + - 1.9.3 + - jruby-19mode + +before_script: "sudo ntpdate -ub ntp.ubuntu.com pool.ntp.org; true" +script: "bundle exec rake clean test cucumber" + +gemfile: + - gemfiles/3.0.gemfile + - gemfiles/3.1.gemfile + - gemfiles/3.2.gemfile + +matrix: + allow_failures: + - rvm: jruby-19mode diff --git a/vendor/gems/paperclip/Appraisals b/vendor/gems/paperclip/Appraisals new file mode 100644 index 0000000..4214635 --- /dev/null +++ b/vendor/gems/paperclip/Appraisals @@ -0,0 +1,14 @@ +appraise "3.0" do + gem "rails", "~> 3.0.15" + gem "paperclip", :path => "../" +end + +appraise "3.1" do + gem "rails", "~> 3.1.6" + gem "paperclip", :path => "../" +end + +appraise "3.2" do + gem "rails", "~> 3.2.6" + gem "paperclip", :path => "../" +end diff --git a/vendor/gems/paperclip/CONTRIBUTING.md b/vendor/gems/paperclip/CONTRIBUTING.md new file mode 100644 index 0000000..505e089 --- /dev/null +++ b/vendor/gems/paperclip/CONTRIBUTING.md @@ -0,0 +1,70 @@ +Contributing +============ + +We love pull requests. Here's a quick guide: + +1. Fork the repo. + +2. Run the tests. We only take pull requests with passing tests, and it's great +to know that you have a clean slate: `bundle && rake` + +3. Add a test for your change. Only refactoring and documentation changes +require no new tests. If you are adding functionality or fixing a bug, we need +a test! + +4. Make the test pass. + +5. Push to your fork and submit a pull request. + +At this point you're waiting on us. We like to at least comment on, if not +accept, pull requests within three business days (and, typically, one business +day). We may suggest some changes or improvements or alternatives. + +Some things that will increase the chance that your pull request is accepted, +taken straight from the Ruby on Rails guide: + +* Use Rails idioms and helpers +* Include tests that fail without your code, and pass with it +* Update the documentation, the surrounding one, examples elsewhere, guides, + whatever is affected by your contribution + +Running Tests +------------- + +Paperclip uses [Appraisal](https://github.com/thoughtbot/appraisal) to aid +testing against multiple version of Ruby on Rails. This helps us to make sure +that Paperclip performs correctly with them. + +### Bootstrapping your test suite: + + bundle install + bundle exec rake appraisal:install + +This will install all the required gems that requires to test against each +version of Rails, which defined in `gemfiles/*.gemfile`. + +### To run a full test suite: + + bundle exec rake + +This will run Test::Unit and Cucumber against all version of Rails + +### To run single Test::Unit or Cucumber test + +You need to specify a `BUNDLE_GEMFILE` pointing to the gemfile before running +the normal test command: + + BUNDLE_GEMFILE=gemfiles/3.2.gemfile ruby -Itest test/schema_test.rb + BUNDLE_GEMFILE=gemfiles/3.2.gemfile cucumber features/basic_integration.feature + +Syntax +------ + +* Two spaces, no tabs. +* No trailing whitespace. Blank lines should not have any space. +* Prefer &&/|| over and/or. +* MyClass.my_method(my_arg) not my_method( my_arg ) or my_method my_arg. +* a = b and not a=b. +* Follow the conventions you see used in the source already. + +And in case we didn't emphasize it enough: we love tests! diff --git a/vendor/gems/paperclip/Gemfile b/vendor/gems/paperclip/Gemfile new file mode 100644 index 0000000..1a05ab2 --- /dev/null +++ b/vendor/gems/paperclip/Gemfile @@ -0,0 +1,7 @@ +source "http://rubygems.org" + +gemspec + +gem "jruby-openssl", :platform => :jruby +gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby +gem "sqlite3", :platform => :ruby diff --git a/vendor/gems/paperclip/LICENSE b/vendor/gems/paperclip/LICENSE new file mode 100644 index 0000000..299b9ed --- /dev/null +++ b/vendor/gems/paperclip/LICENSE @@ -0,0 +1,26 @@ + +LICENSE + +The MIT License + +Copyright (c) 2008 Jon Yurek and 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. + + diff --git a/vendor/gems/paperclip/NEWS b/vendor/gems/paperclip/NEWS new file mode 100644 index 0000000..1c4e840 --- /dev/null +++ b/vendor/gems/paperclip/NEWS @@ -0,0 +1,178 @@ +New in 3.1.4: + +* Bug fix: Allow user to be able to set path without `:style` attribute and not raising an error. + This is a regression introduced in 3.1.3, and that feature will be postponed to another minor + release instead. +* Feature: Allow for URI Adapter as an optional paperclip io adapter. + +New in 3.1.3: + +* Bug fix: Copy empty attachment between instances is now working. +* Bug fix: Correctly rescue Fog error. +* Bug fix: Using default path and url options in Fog storage now work as expected. +* Bug fix: `Attachment#s3_protocol` now returns a protocol without colon suffix. +* Feature: Paperclip will now raise an error if multiple styles are defined but no `:style` + interpolation exists in `:path`. +* Feature: Add support for `#{attachment}_created_at` field +* Bug fix: Paperclip now gracefully handles msising file command. +* Bug fix: `StringIOAdapter` now accepts content type. + +New in 3.1.2: + +* Bug fix: #remove_attachment on 3.1.0 and 3.1.1 mistakenly trying to remove the column that has + the same name as data type (such as :string, :datetime, :interger.) You're advised to update to + Paperclip 3.1.2 as soon as possible. + +New in 3.1.1: + +* Bug fix: Paperclip will only load Paperclip::Schema only when Active Record is available. + +New in 3.1.0: + +* Feature: Paperclip now support new migration syntax (sexy migration) that reads better: + + class AddAttachmentToUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.attachment :avatar + end + end + end + + Also, schema-definition level syntax has been added: + + add_attachment :users, :avatar + remove_attachment :users, :avatar + +* Feature: Migration now support Rails 3.2+ `change` method. +* API CHANGE: Old `t.has_attached_file` and `drop_attached_file` are now deprecated. You're advised + to update your migration file before the next MAJOR version. +* Bug fix: Tempfile now rewinded before generating fingerprint +* API CHANGE: Tempfiles are now unlinked after `after_flush_writes` + + If you need to interact with the generated tempfiles, please define an `after_flush_writes` method + in your model. You'll be able to access files via `@queue_for_write` instance variable. + +* Bug fix: `:s3_protocol` can now be defined as either String or Symbol +* Bug fix: Tempfiles are now rewinded before get passed into `after_flush_writes` +* Feature: Added expiring_url method to Fog Storage +* API CHANGE: Paperclip now tested against AWS::SDK 1.5.2 onward +* Bug fix: Improved the output of the content_type validator so the actual failure is displayed +* Feature: Animated formats now identified using ImageMagick. +* Feature: AttachmentAdapter now support fetching attachment with specific style. +* Feature: Paperclip default options can now be configured in Rails.configuration. +* Feature: add Geometry#resize_to to calculate dimensions of new source. +* Bug fix: Fixed a bug whereby a file type with multiple mime types but no official type would cause + the best_content_type to throw an error on trying nil.content_type. +* Bug fix: Fix problem when the gem cannot be installed on the system that has Asepsis installed. + +New in 3.0.4: + +* Feature: Adds support for S3 scheme-less URL generation. + +New in 3.0.3: + +* Bug fix: ThumbnailProcessor now correctly detects and preserve animated GIF. +* Bug fix: File extension is now preserved in generated Tempfile from adapter. +* Bug fix: Uploading file with unicode file name now won't raise an error when + logging in the AWS is turned on. +* Bug fix: Task "paperclip:refresh:missing_styles" now work correctly. +* Bug fix: Handle the case when :restricted_characters is nil. +* Bug fix: Don't delete all the existing styles if we reprocess. +* Bug fix: Content type is now ensured to not having a new line character. +* API CHANGE: Non-Rails usage should include Paperclip::Glue directly. + + `Paperclip::Railtie` was intended to be used with Ruby on Rails only. If you're + using Paperclip without Rails, you should include `Paperclip::Glue` into + `ActiveRecord::Base` instead of requiring `paperclip/railtie`: + + ActiveRecord::Base.send :include, Paperclip::Glue + +* Bug fix: AttachmentContentTypeValidator now allow you to specify :allow_blank/:allow_nil +* Bug fix: Make sure content type always a String. +* Bug fix: Fix attachment.reprocess! when using storage providers fog and s3. +* Bug fix: Fix a problem with incorrect content_type detected with 'file' command for an empty file on Mac. + +New in 3.0.2: + +* API CHANGE: Generated migration class name is now plural (AddAttachmentToUsers instead of AddAttachmentToUser) +* API CHANGE: Remove Rails plugin initialization code. +* API CHANGE: Explicitly require Ruby 1.9.2 in the Gemfile. +* Bug fix: Fixes AWS::S3::Errors::RequestTimeout on Model#save. +* Bug fix: Fix a problem when there's no logger specified. +* Bug fix: Fix a problem when attaching Rack::Test::UploadedFile instance. + +New in 3.0.1: + +* Feature: Introduce Paperlip IO adapter. +* Bug fix: Regression in AttachmentContentTypeValidator has been fixed. + +New in 3.0.0: + +* API CHANGE: Paperclip now requires at least Ruby on Rails version 3.0.0 +* API CHANGE: The default :url and :path have changed. The new scheme avoids + filesystem conflicts and scales to handle larger numbers of uploads. + + The easiest way to upgrade is to add an explicit :url and :path to your + has_attached_file calls: + + has_attached_file :avatar, + :path => ":rails_root/public/system/:attachment/:id/:style/:filename", + :url => "/system/:attachment/:id/:style/:filename" + +* Feature: Adding Rails 3 style validators, and adding `validates_attachment` method as a shorthand. +* Bug fix: Paperclip's rake tasks now loading records in batch. +* Bug fix: Attachment style name with leading number now not raising an error. +* Bug fix: File given to S3 and Fog storage will now be rewinded after flush_write. +* Feature: You can now pass addional parameter to S3 expiring URL, such as :content_type. + +New in 2.7.0: + +* Bug fix: Checking the existence of a file on S3 handles all AWS errors. +* Bug fix: Clear the fingerprint when removing an attachment. +* Bug fix: Attachment size validation message reads more nicely now. +* Feature: Style names can be either symbols or strings. +* Compatibility: Support for ActiveSupport < 2.3.12. +* Compatibility: Support for Rails 3.2. + +New in 2.6.0: + +* Bug fix: Files are re-wound after reading. +* Feature: Remove Rails dependency from specs that need Paperclip. +* Feature: Validation matchers support conditionals. + +New in 2.5.2: + +* Bug fix: Can be installed on Windows. +* Feature: The Fog bucket name, authentication, and host can be determined at runtime via Proc. +* Feature: Special characters are replaced with underscores in #url and #path. + +New in 2.5.1: + +* Feature: After we've computed the content type, pass it to Fog. +* Feature: S3 encryption with the new :s3_server_side_encryption option. +* Feature: Works without ActiveRecord, allowing for e.g. mongo backends. + +New in 2.5.0: + +* Performance: Only connect to S3 when absolutely needed. +* Bug fix: STI with cached classes respect new options. +* Bug fix: conditional validations broke, and now work again. +* Feature: URL generation is now parameterized and can be changed with plugins or custom code. +* Feature: :convert_options and :source_file_options to control the ImageMagick processing. +* Performance: String geometry specifications now parse more quickly. +* Bug fix: Handle files with question marks in the filename. +* Bug fix: Don't raise an error when generating an expiring URL on an unassigned attachment. +* Bug fix: The rake task runs over all instances of an ActiveRecord model, ignoring default scopes. +* Feature: DB migration has_attached_file and drop_attached_file methods. +* Bug fix: Switch from AWS::S3 to AWS::SDK for the S3 backend. +* Bug fix: URL generator uses '?' in the URL unless it already appears and there is no prior '='. +* Bug fix: Always convert the content type to a string before stripping blanks. +* Feature: The :keep_old_files option preserves the files in storage even when the attachment is cleared or changed. +* Performance: Optimize Fog's public_url access by avoiding it when possible. +* Bug fix: Avoid a runtime error when generating the ID partition for an unsaved attachment. +* Performance: Do not calculate the fingerprint if it is never persisted. +* Bug fix: Process the :original style before all others, in case of a dependency. +* Feature: S3 headers can be set at runtime by passing a proc object as the value. +* Bug fix: Generating missing attachment styles for a model which has had its attachment changed should not raise. +* Bug fix: Do not collide with the built-in Ruby hashing method. diff --git a/vendor/gems/paperclip/README.md b/vendor/gems/paperclip/README.md new file mode 100644 index 0000000..fe5d0be --- /dev/null +++ b/vendor/gems/paperclip/README.md @@ -0,0 +1,567 @@ +Paperclip +========= + +[![Build Status](https://secure.travis-ci.org/thoughtbot/paperclip.png?branch=master)](http://travis-ci.org/thoughtbot/paperclip) [![Dependency Status](https://gemnasium.com/thoughtbot/paperclip.png?travis)](https://gemnasium.com/thoughtbot/paperclip) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/thoughtbot/paperclip) + +Paperclip is intended as an easy file attachment library for Active Record. The +intent behind it was to keep setup as easy as possible and to treat files as +much like other attributes as possible. This means they aren't saved to their +final locations on disk, nor are they deleted if set to nil, until +ActiveRecord::Base#save is called. It manages validations based on size and +presence, if required. It can transform its assigned image into thumbnails if +needed, and the prerequisites are as simple as installing ImageMagick (which, +for most modern Unix-based systems, is as easy as installing the right +packages). Attached files are saved to the filesystem and referenced in the +browser by an easily understandable specification, which has sensible and +useful defaults. + +See the documentation for `has_attached_file` in [`Paperclip::ClassMethods`](http://rubydoc.info/gems/paperclip/Paperclip/ClassMethods) for +more detailed options. + +The complete [RDoc](http://rdoc.info/gems/paperclip) is online. + + +Requirements +------------ + +### Ruby and Rails + +Paperclip now requires Ruby version **>= 1.9.2** and Rails version **>= 3.0** (Only if you're going to use Paperclip with Ruby on Rails.) + +If you're still on Ruby 1.8.7 or Ruby on Rails 2.3.x, you can still use Paperclip 2.7.x with your project. Also, everything in this README might not apply to your version of Paperclip, and you should read [the README for version 2.7](http://rubydoc.info/gems/paperclip/2.7.0) instead. + +### Image Processor + +[ImageMagick](http://www.imagemagick.org) must be installed and Paperclip must have access to it. To ensure +that it does, on your command line, run `which convert` (one of the ImageMagick +utilities). This will give you the path where that utility is installed. For +example, it might return `/usr/local/bin/convert`. + +Then, in your environment config file, let Paperclip know to look there by adding that +directory to its path. + +In development mode, you might add this line to `config/environments/development.rb)`: + + Paperclip.options[:command_path] = "/usr/local/bin/" + +If you're on Mac OS X, you'll want to run the following with Homebrew: + + brew install imagemagick + +If you are dealing with pdf uploads or running the test suite, you'll also need +GhostScript to be installed. On Mac OS X, you can also install that using Homebrew: + + brew install gs + + +Installation +------------ + +Paperclip is distributed as a gem, which is how it should be used in your app. + +Include the gem in your Gemfile: + + gem "paperclip", "~> 3.0" + +If you're still using Rails 2.3.x, you should do this instead: + + gem "paperclip", "~> 2.7" + +Or, if you want to get the latest, you can get master from the main paperclip repository: + + gem "paperclip", :git => "git://github.com/thoughtbot/paperclip.git" + +If you're trying to use features that don't seem to be in the latest released gem, but are +mentioned in this README, then you probably need to specify the master branch if you want to +use them. This README is probably ahead of the latest released version, if you're reading it +on GitHub. + +For Non-Rails usage: + + class ModuleName < ActiveRecord::Base + include Paperclip::Glue + ... + end + +Quick Start +----------- + +In your model: + + class User < ActiveRecord::Base + attr_accessible :avatar + has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" } + end + +In your migrations: + + class AddAvatarColumnsToUsers < ActiveRecord::Migration + def self.up + add_attachment :users, :avatar + end + + def self.down + remove_attachment :users, :avatar + end + end + +(Or you can use migration generator: `rails generate paperclip user avatar`) + +In your edit and new views: + + <%= form_for @user, :url => users_path, :html => { :multipart => true } do |form| %> + <%= form.file_field :avatar %> + <% end %> + +In your controller: + + def create + @user = User.create( params[:user] ) + end + +In your show view: + + <%= image_tag @user.avatar.url %> + <%= image_tag @user.avatar.url(:medium) %> + <%= image_tag @user.avatar.url(:thumb) %> + +To detach a file, simply set the attribute to `nil`: + + @user.avatar = nil + @user.save + +Usage +----- + +The basics of paperclip are quite simple: Declare that your model has an +attachment with the `has_attached_file` method, and give it a name. + +Paperclip will wrap up up to four attributes (all prefixed with that attachment's name, +so you can have multiple attachments per model if you wish) and give them a +friendly front end. These attributes are: + +* `_file_name` +* `_file_size` +* `_content_type` +* `_updated_at` + +By default, only `_file_name` is required for paperclip to operate. +You'll need to add `_content_type` in case you want to use content type +validation. + +More information about the options to `has_attached_file` is available in the +documentation of [`Paperclip::ClassMethods`](http://rubydoc.info/gems/paperclip/Paperclip/ClassMethods). + +For validations, Paperclip introduces several validators to validate your attachment: + +* `AttachmentContentTypeValidator` +* `AttachmentPresenceValidator` +* `AttachmentSizeValidator` + +Example Usage: + + validates :avatar, :attachment_presence => true + validates_with AttachmentPresenceValidator, :attributes => :avatar + +Validators can also be defined using the old helper style: + +* `validates_attachment_presence` +* `validates_attachment_content_type` +* `validates_attachment_size` + +Example Usage: + + validates_attachment_presence :avatar + +Lastly, you can also define multiple validations on a single attachment using `validates_attachment`: + + validates_attachment :avatar, :presence => true, + :content_type => { :content_type => "image/jpg" }, + :size => { :in => 0..10.kilobytes } + +Defaults +-------- +Global defaults for all your paperclip attachments can be defined by changing the Paperclip::Attachment.default_options Hash, this can be useful for setting your default storage settings per example so you won't have to define them in every has_attached_file definition. + +If you're using Rails you can define a Hash with default options in config/application.rb or in any of the config/environments/*.rb files on config.paperclip_defaults, these well get merged into Paperclip::Attachment.default_options as your Rails app boots. An example: + +```ruby +module YourApp + class Application < Rails::Application + # Other code... + + config.paperclip_defaults = {:storage => :fog, :fog_credentials => {:provider => "Local", :local_root => "#{Rails.root}/public"}, :fog_directory => "", :fog_host => "localhost"} + end +end +``` + +Another option is to directly modify the Paperclip::Attachment.default_options Hash, this method works for non-Rails applications or is an option if you prefer to place the Paperclip default settings in an initializer. + +An example Rails initializer would look something like this: + +```ruby +Paperclip::Attachment.default_options[:storage] = :fog +Paperclip::Attachment.default_options[:fog_credentials] = {:provider => "Local", :local_root => "#{Rails.root}/public"} +Paperclip::Attachment.default_options[:fog_directory] = "" +Paperclip::Attachment.default_options[:fog_host] = "http://localhost:3000" +``` + +Migrations +---------- + +Paperclip defines several migration methods which can be used to create necessary columns in your +model. There are two types of method: + +### Table Definition + + class AddAttachmentToUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.attachment :avatar + end + end + end + +If you're using Rails 3.2 or newer, this method works in `change` method as well: + + class AddAttachmentToUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.attachment :avatar + end + end + end + +### Schema Definition + + class AddAttachmentToUsers < ActiveRecord::Migration + def self.up + add_attachment :users, :avatar + end + + def self.down + remove_attachment :users, :avatar + end + end + +If you're using Rails 3.2 or newer, you only need `add_attachment` in your `change` method: + + class AddAttachmentToUsers < ActiveRecord::Migration + def change + add_attachment :users, :avatar + end + end + +### Vintage syntax + +Vintage syntax (such as `t.has_attached_file` and `drop_attached_file`) are still supported in +Paperclip 3.x, but you're advised to update those migration files to use this new syntax. + +Storage +------- + +Paperclip ships with 3 storage adapters: + +* File Storage +* S3 Storage (via `aws-sdk`) +* Fog Storage + +If you would like to use Paperclip with another storage, you can install these +gems along side with Paperclip: + +* [Windows Azure](https://github.com/gmontard/paperclip-azure-storage) + +### Understanding Storage + +The files that are assigned as attachments are, by default, placed in the +directory specified by the `:path` option to `has_attached_file`. By default, this +location is `:rails_root/public/system/:class/:attachment/:id_partition/:style/:filename`. +This location was chosen because on standard Capistrano deployments, the +`public/system` directory is symlinked to the app's shared directory, meaning it +will survive between deployments. For example, using that `:path`, you may have a +file at + + /data/myapp/releases/20081229172410/public/system/users/avatar/000/000/013/small/my_pic.png + +_**NOTE**: This is a change from previous versions of Paperclip, but is overall a +safer choice for the default file store._ + +You may also choose to store your files using Amazon's S3 service. To do so, include +the `aws-sdk` gem in your Gemfile: + + gem 'aws-sdk', '~> 1.3.4' + +And then you can specify using S3 from `has_attached_file`. +You can find more information about configuring and using S3 storage in +[the `Paperclip::Storage::S3` documentation](http://rubydoc.info/gems/paperclip/Paperclip/Storage/S3). + +Files on the local filesystem (and in the Rails app's public directory) will be +available to the internet at large. If you require access control, it's +possible to place your files in a different location. You will need to change +both the `:path` and `:url` options in order to make sure the files are unavailable +to the public. Both `:path` and `:url` allow the same set of interpolated +variables. + +Post Processing +--------------- + +Paperclip supports an extensible selection of post-processors. When you define +a set of styles for an attachment, by default it is expected that those +"styles" are actually "thumbnails". However, you can do much more than just +thumbnail images. By defining a subclass of Paperclip::Processor, you can +perform any processing you want on the files that are attached. Any file in +your Rails app's lib/paperclip\_processors directory is automatically loaded by +paperclip, allowing you to easily define custom processors. You can specify a +processor with the :processors option to `has_attached_file`: + + has_attached_file :scan, :styles => { :text => { :quality => :better } }, + :processors => [:ocr] + +This would load the hypothetical class Paperclip::Ocr, which would have the +hash "{ :quality => :better }" passed to it along with the uploaded file. For +more information about defining processors, see Paperclip::Processor. + +The default processor is Paperclip::Thumbnail. For backwards compatibility +reasons, you can pass a single geometry string or an array containing a +geometry and a format, which the file will be converted to, like so: + + has_attached_file :avatar, :styles => { :thumb => ["32x32#", :png] } + +This will convert the "thumb" style to a 32x32 square in png format, regardless +of what was uploaded. If the format is not specified, it is kept the same (i.e. +jpgs will remain jpgs). For more information on the accepted style formats, see +[here](http://www.imagemagick.org/script/command-line-processing.php#geometry). + +Multiple processors can be specified, and they will be invoked in the order +they are defined in the :processors array. Each successive processor will +be given the result of the previous processor's execution. All processors will +receive the same parameters, which are what you define in the :styles hash. +For example, assuming we had this definition: + + has_attached_file :scan, :styles => { :text => { :quality => :better } }, + :processors => [:rotator, :ocr] + +then both the :rotator processor and the :ocr processor would receive the +options "{ :quality => :better }". This parameter may not mean anything to one +or more or the processors, and they are expected to ignore it. + +_NOTE: Because processors operate by turning the original attachment into the +styles, no processors will be run if there are no styles defined._ + +If you're interested in caching your thumbnail's width, height and size in the +database, take a look at the [paperclip-meta](https://github.com/y8/paperclip-meta) gem. + +Also, if you're interested in generating the thumbnail on-the-fly, you might want +to look into the [attachment_on_the_fly](https://github.com/drpentode/Attachment-on-the-Fly) gem. + +Events +------ + +Before and after the Post Processing step, Paperclip calls back to the model +with a few callbacks, allowing the model to change or cancel the processing +step. The callbacks are `before_post_process` and `after_post_process` (which +are called before and after the processing of each attachment), and the +attachment-specific `before__post_process` and +`after__post_process`. The callbacks are intended to be as close to +normal ActiveRecord callbacks as possible, so if you return false (specifically +\- returning nil is not the same) in a `before_filter`, the post processing step +will halt. Returning false in an `after_filter` will not halt anything, but you +can access the model and the attachment if necessary. + +_NOTE: Post processing will not even *start* if the attachment is not valid +according to the validations. Your callbacks and processors will *only* be +called with valid attachments._ + + class Message < ActiveRecord::Base + has_attached_file :asset, styles: {thumb: "100x100#"} + + before_post_process :skip_for_audio + + def skip_for_audio + ! %w(audio/ogg application/ogg).include?(asset_content_type) + end + end + +URI Obfuscation +--------------- + +Paperclip has an interpolation called `:hash` for obfuscating filenames of +publicly-available files. + +Example Usage: + + has_attached_file :avatar, { + :url => "/system/:hash.:extension", + :hash_secret => "longSecretString" + } + + +The `:hash` interpolation will be replaced with a unique hash made up of whatever +is specified in `:hash_data`. The default value for `:hash_data` is `":class/:attachment/:id/:style/:updated_at"`. + +`:hash_secret` is required, an exception will be raised if `:hash` is used without `:hash_secret` present. + +For more on this feature read the author's own explanation. [https://github.com/thoughtbot/paperclip/pull/416](https://github.com/thoughtbot/paperclip/pull/416) + +MD5 Checksum / Fingerprint +------- + +A MD5 checksum of the original file assigned will be placed in the model if it +has an attribute named fingerprint. Following the user model migration example +above, the migration would look like the following. + + class AddAvatarFingerprintColumnToUser < ActiveRecord::Migration + def self.up + add_column :users, :avatar_fingerprint, :string + end + + def self.down + remove_column :users, :avatar_fingerprint + end + end + +Custom Attachment Processors +------- + +Custom attachment processors can be implemented and their only requirement is +to inherit from `Paperclip::Processor` (see `lib/paperclip/processor.rb`). +For example, when `:styles` are specified for an image attachment, the +thumbnail processor (see `lib/paperclip/thumbnail.rb`) is loaded without having +to specify it as a `:processor` parameter to `has_attached_file`. When any +other processor is defined it must be called out in the `:processors` +parameter if it is to be applied to the attachment. The thumbnail processor +uses the imagemagick `convert` command to do the work of resizing image +thumbnails. It would be easy to create a custom processor that watermarks +an image using imagemagick's `composite` command. Following the +implementation pattern of the thumbnail processor would be a way to implement a +watermark processor. All kinds of attachment processors can be created; +a few utility examples would be compression and encryption processors. + + +Dynamic Configuration +--------------------- + +Callable objects (lambdas, Procs) can be used in a number of places for dynamic +configuration throughout Paperclip. This strategy exists in a number of +components of the library but is most significant in the possibilities for +allowing custom styles and processors to be applied for specific model +instances, rather than applying defined styles and processors across all +instances. + +### Dynamic Styles: + +Imagine a user model that had different styles based on the role of the user. +Perhaps some users are bosses (e.g. a User model instance responds to #boss?) +and merit a bigger avatar thumbnail than regular users. The configuration to +determine what style parameters are to be used based on the user role might +look as follows where a boss will receive a `300x300` thumbnail otherwise a +`100x100` thumbnail will be created. + + class User < ActiveRecord::Base + has_attached_file :avatar, :styles => lambda { |attachment| { :thumb => (attachment.instance.boss? ? "300x300>" : "100x100>") } + end + +### Dynamic Processors: + +Another contrived example is a user model that is aware of which file processors +should be applied to it (beyond the implied `thumbnail` processor invoked when +`:styles` are defined). Perhaps we have a watermark processor available and it is +only used on the avatars of certain models. The configuration for this might be +where the instance is queried for which processors should be applied to it. +Presumably some users might return `[:thumbnail, :watermark]` for its +processors, where a defined `watermark` processor is invoked after the +`thumbnail` processor already defined by Paperclip. + + class User < ActiveRecord::Base + has_attached_file :avatar, :processors => lambda { |instance| instance.processors } + attr_accessor :watermark + end + +Deployment +---------- + +Paperclip is aware of new attachment styles you have added in previous deploys. The only thing you should do after each deployment is to call +`rake paperclip:refresh:missing_styles`. It will store current attachment styles in `RAILS_ROOT/public/system/paperclip_attachments.yml` +by default. You can change it by: + + Paperclip.registered_attachments_styles_path = '/tmp/config/paperclip_attachments.yml' + +Here is an example for Capistrano: + + namespace :deploy do + desc "build missing paperclip styles" + task :build_missing_paperclip_styles, :roles => :app do + run "cd #{release_path}; RAILS_ENV=production bundle exec rake paperclip:refresh:missing_styles" + end + end + + after("deploy:update_code", "deploy:build_missing_paperclip_styles") + +Now you don't have to remember to refresh thumbnails in production every time you add a new style. +Unfortunately it does not work with dynamic styles - it just ignores them. + +If you already have a working app and don't want `rake paperclip:refresh:missing_styles` to refresh old pictures, you need to tell +Paperclip about existing styles. Simply create a `paperclip_attachments.yml` file by hand. For example: + + class User < ActiveRecord::Base + has_attached_file :avatar, :styles => {:thumb => 'x100', :croppable => '600x600>', :big => '1000x1000>'} + end + + class Book < ActiveRecord::Base + has_attached_file :cover, :styles => {:small => 'x100', :large => '1000x1000>'} + has_attached_file :sample, :styles => {:thumb => 'x100'} + end + +Then in `RAILS_ROOT/public/system/paperclip_attachments.yml`: + + --- + :User: + :avatar: + - :thumb + - :croppable + - :big + :Book: + :cover: + - :small + - :large + :sample: + - :thumb + +Testing +------- + +Paperclip provides rspec-compatible matchers for testing attachments. See the +documentation on [Paperclip::Shoulda::Matchers](http://rubydoc.info/gems/paperclip/Paperclip/Shoulda/Matchers) +for more information. + +Contributing +------------ + +If you'd like to contribute a feature or bugfix: Thanks! To make sure your +fix/feature has a high chance of being included, please read the following +guidelines: + +1. Ask on the [mailing list](http://groups.google.com/group/paperclip-plugin), or + post a new [GitHub Issue](http://github.com/thoughtbot/paperclip/issues). +2. Make sure there are tests! We will not accept any patch that is not tested. + It's a rare time when explicit tests aren't needed. If you have questions + about writing tests for paperclip, please ask the mailing list. + +Please see `CONTRIBUTING.md` for more details on contributing and running test. + +Credits +------- + +![thoughtbot](http://thoughtbot.com/images/tm/logo.png) + +Paperclip is maintained and funded by [thoughtbot, inc](http://thoughtbot.com/community) + +Thank you to all [the contributors](https://github.com/thoughtbot/paperclip/contributors)! + +The names and logos for thoughtbot are trademarks of thoughtbot, inc. + +License +------- + +Paperclip is Copyright © 2008-2011 thoughtbot. It is free software, and may be +redistributed under the terms specified in the MIT-LICENSE file. diff --git a/vendor/gems/paperclip/RUNNING_TESTS.md b/vendor/gems/paperclip/RUNNING_TESTS.md new file mode 100644 index 0000000..7ba2156 --- /dev/null +++ b/vendor/gems/paperclip/RUNNING_TESTS.md @@ -0,0 +1,4 @@ +Running Tests +============= + +Please see `CONTRIBUTING.md` in "Running Tests" section for more information. diff --git a/vendor/gems/paperclip/Rakefile b/vendor/gems/paperclip/Rakefile new file mode 100644 index 0000000..b107e9f --- /dev/null +++ b/vendor/gems/paperclip/Rakefile @@ -0,0 +1,46 @@ +require 'bundler/gem_tasks' +require 'appraisal' +require 'rake/testtask' +require 'cucumber/rake/task' + +desc 'Default: run unit tests.' +task :default => [:clean, :all] + +desc 'Test the paperclip plugin under all supported Rails versions.' +task :all do |t| + if ENV['BUNDLE_GEMFILE'] + exec('rake test cucumber') + else + Rake::Task["appraisal:install"].execute + exec('rake appraisal test cucumber') + end +end + +desc 'Test the paperclip plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' << 'profile' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Run integration test' +Cucumber::Rake::Task.new do |t| + t.cucumber_opts = %w{--format progress} +end + +desc 'Start an IRB session with all necessary files required.' +task :shell do |t| + chdir File.dirname(__FILE__) + exec 'irb -I lib/ -I lib/paperclip -r rubygems -r active_record -r tempfile -r init' +end + +desc 'Clean up files.' +task :clean do |t| + FileUtils.rm_rf "doc" + FileUtils.rm_rf "tmp" + FileUtils.rm_rf "pkg" + FileUtils.rm_rf "public" + FileUtils.rm "test/debug.log" rescue nil + FileUtils.rm "test/paperclip.db" rescue nil + Dir.glob("paperclip-*.gem").each{|f| FileUtils.rm f } +end diff --git a/vendor/gems/paperclip/UPGRADING b/vendor/gems/paperclip/UPGRADING new file mode 100644 index 0000000..ba30d45 --- /dev/null +++ b/vendor/gems/paperclip/UPGRADING @@ -0,0 +1,14 @@ +################################################## +# NOTE FOR UPGRADING FROM PRE-3.0 VERSION # +################################################## + +Paperclip 3.0 introduces a non-backward compatible change in your attachment +path. This will help to prevent attachment name clashes when you have +multiple attachments with the same name. If you didn't alter your +attachment's path and are using Paperclip's default, you'll have to add +`:path` and `:url` to your `has_attached_file` definition. For example: + + has_attached_file :avatar, + :path => ":rails_root/public/system/:attachment/:id/:style/:filename", + :url => "/system/:attachment/:id/:style/:filename" + diff --git a/vendor/gems/paperclip/cucumber/paperclip_steps.rb b/vendor/gems/paperclip/cucumber/paperclip_steps.rb new file mode 100644 index 0000000..fac0c12 --- /dev/null +++ b/vendor/gems/paperclip/cucumber/paperclip_steps.rb @@ -0,0 +1,6 @@ +When /^I attach an? "([^\"]*)" "([^\"]*)" file to an? "([^\"]*)" on S3$/ do |attachment, extension, model| + stub_paperclip_s3(model, attachment, extension) + attach_file attachment, + "features/support/paperclip/#{model.gsub(" ", "_").underscore}/#{attachment}.#{extension}" +end + diff --git a/vendor/gems/paperclip/features/basic_integration.feature b/vendor/gems/paperclip/features/basic_integration.feature new file mode 100644 index 0000000..ed41db5 --- /dev/null +++ b/vendor/gems/paperclip/features/basic_integration.feature @@ -0,0 +1,68 @@ +Feature: Rails integration + + Background: + Given I generate a new rails application + And I run a rails generator to generate a "User" scaffold with "name:string" + And I run a paperclip generator to add a paperclip "attachment" to the "User" model + And I run a migration + And I update my new user view to include the file upload field + And I update my user view to include the attachment + + Scenario: Configure defaults for all attachments through Railtie + Given I add this snippet to config/application.rb: + """ + config.paperclip_defaults = {:url => "/paperclip/custom/:attachment/:style/:filename"} + """ + Given I add this snippet to the User model: + """ + attr_accessible :name, :attachment + has_attached_file :attachment + """ + And I start the rails application + When I go to the new user page + And I fill in "Name" with "something" + And I attach the file "test/fixtures/5k.png" to "Attachment" + And I press "Submit" + Then I should see "Name: something" + And I should see an image with a path of "/paperclip/custom/attachments/original/5k.png" + And the file at "/paperclip/custom/attachments/original/5k.png" should be the same as "test/fixtures/5k.png" + + Scenario: Filesystem integration test + Given I add this snippet to the User model: + """ + attr_accessible :name, :attachment + has_attached_file :attachment, :url => "/system/:attachment/:style/:filename" + """ + And I start the rails application + When I go to the new user page + And I fill in "Name" with "something" + And I attach the file "test/fixtures/5k.png" to "Attachment" + And I press "Submit" + Then I should see "Name: something" + And I should see an image with a path of "/system/attachments/original/5k.png" + And the file at "/system/attachments/original/5k.png" should be the same as "test/fixtures/5k.png" + + Scenario: S3 Integration test + Given I add this snippet to the User model: + """ + attr_accessible :name, :attachment + has_attached_file :attachment, + :storage => :s3, + :path => "/:attachment/:style/:filename", + :s3_credentials => Rails.root.join("config/s3.yml"), + :styles => { :square => "100x100#" } + """ + And I write to "config/s3.yml" with: + """ + bucket: paperclip + access_key_id: access_key + secret_access_key: secret_key + """ + And I start the rails application + When I go to the new user page + And I fill in "Name" with "something" + And I attach the file "test/fixtures/5k.png" to "Attachment" on S3 + And I press "Submit" + Then I should see "Name: something" + And I should see an image with a path of "http://s3.amazonaws.com/paperclip/attachments/original/5k.png" + And the file at "http://s3.amazonaws.com/paperclip/attachments/original/5k.png" should be uploaded to S3 diff --git a/vendor/gems/paperclip/features/migration.feature b/vendor/gems/paperclip/features/migration.feature new file mode 100644 index 0000000..8d000c8 --- /dev/null +++ b/vendor/gems/paperclip/features/migration.feature @@ -0,0 +1,94 @@ +Feature: Migration + + Background: + Given I generate a new rails application + And I write to "app/models/user.rb" with: + """ + class User < ActiveRecord::Base; end + """ + + Scenario: Vintage syntax + When I write to "db/migrate/01_add_attachment_to_users.rb" with: + """ + class AddAttachmentToUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.has_attached_file :avatar + end + end + + def self.down + drop_attached_file :users, :avatar + end + end + """ + And I run a migration + Then I should have attachment columns for "avatar" + + When I rollback a migration + Then I should not have attachment columns for "avatar" + + Scenario: New syntax with create_table + When I write to "db/migrate/01_add_attachment_to_users.rb" with: + """ + class AddAttachmentToUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.attachment :avatar + end + end + end + """ + And I run a migration + Then I should have attachment columns for "avatar" + + Scenario: New syntax outside of create_table + When I write to "db/migrate/01_create_users.rb" with: + """ + class CreateUsers < ActiveRecord::Migration + def self.up + create_table :users + end + end + """ + And I write to "db/migrate/02_add_attachment_to_users.rb" with: + """ + class AddAttachmentToUsers < ActiveRecord::Migration + def self.up + add_attachment :users, :avatar + end + + def self.down + remove_attachment :users, :avatar + end + end + """ + And I run a migration + Then I should have attachment columns for "avatar" + + When I rollback a migration + Then I should not have attachment columns for "avatar" + + Scenario: Rails 3.2 change method + Given I am using Rails newer than 3.1 + When I write to "db/migrate/01_create_users.rb" with: + """ + class CreateUsers < ActiveRecord::Migration + def self.up + create_table :users + end + end + """ + When I write to "db/migrate/02_add_attachment_to_users.rb" with: + """ + class AddAttachmentToUsers < ActiveRecord::Migration + def change + add_attachment :users, :avatar + end + end + """ + And I run a migration + Then I should have attachment columns for "avatar" + + When I rollback a migration + Then I should not have attachment columns for "avatar" diff --git a/vendor/gems/paperclip/features/rake_tasks.feature b/vendor/gems/paperclip/features/rake_tasks.feature new file mode 100644 index 0000000..2f7ec88 --- /dev/null +++ b/vendor/gems/paperclip/features/rake_tasks.feature @@ -0,0 +1,63 @@ +Feature: Rake tasks + + Background: + Given I generate a new rails application + And I run a rails generator to generate a "User" scaffold with "name:string" + And I run a paperclip generator to add a paperclip "attachment" to the "User" model + And I run a migration + And I add this snippet to the User model: + """ + attr_accessible :name, :attachment + has_attached_file :attachment, :path => ":rails_root/public/system/:attachment/:style/:filename" + """ + + Scenario: Paperclip refresh thumbnails task + When I modify my attachment definition to: + """ + has_attached_file :attachment, :path => ":rails_root/public/system/:attachment/:style/:filename", + :styles => { :medium => "200x200#" } + """ + And I upload the fixture "5k.png" + Then the attachment "medium/5k.png" should have a dimension of 200x200 + When I modify my attachment definition to: + """ + has_attached_file :attachment, :path => ":rails_root/public/system/:attachment/:style/:filename", + :styles => { :medium => "100x100#" } + """ + When I successfully run `bundle exec rake paperclip:refresh:thumbnails CLASS=User --trace` + Then the attachment "original/5k.png" should exist + And the attachment "medium/5k.png" should have a dimension of 100x100 + + Scenario: Paperclip refresh metadata task + When I upload the fixture "5k.png" + And I swap the attachment "original/5k.png" with the fixture "12k.png" + And I successfully run `bundle exec rake paperclip:refresh:metadata CLASS=User --trace` + Then the attachment should have the same content type as the fixture "12k.png" + And the attachment should have the same file size as the fixture "12k.png" + + Scenario: Paperclip refresh missing styles task + When I upload the fixture "5k.png" + Then the attachment file "original/5k.png" should exist + And the attachment file "medium/5k.png" should not exist + When I modify my attachment definition to: + """ + has_attached_file :attachment, :path => ":rails_root/public/system/:attachment/:style/:filename", + :styles => { :medium => "200x200#" } + """ + When I successfully run `bundle exec rake paperclip:refresh:missing_styles --trace` + Then the attachment file "original/5k.png" should exist + And the attachment file "medium/5k.png" should exist + + Scenario: Paperclip clean task + When I upload the fixture "5k.png" + And I upload the fixture "12k.png" + Then the attachment file "original/5k.png" should exist + And the attachment file "original/12k.png" should exist + When I modify my attachment definition to: + """ + has_attached_file :attachment, :path => ":rails_root/public/system/:attachment/:style/:filename" + validates_attachment_size :attachment, :less_than => 10.kilobytes + """ + And I successfully run `bundle exec rake paperclip:clean CLASS=User --trace` + Then the attachment file "original/5k.png" should exist + But the attachment file "original/12k.png" should not exist diff --git a/vendor/gems/paperclip/features/step_definitions/attachment_steps.rb b/vendor/gems/paperclip/features/step_definitions/attachment_steps.rb new file mode 100644 index 0000000..bc7937c --- /dev/null +++ b/vendor/gems/paperclip/features/step_definitions/attachment_steps.rb @@ -0,0 +1,102 @@ +module AttachmentHelpers + def fixture_path(filename) + File.expand_path("#{PROJECT_ROOT}/test/fixtures/#{filename}") + end + + def attachment_path(filename) + File.expand_path("public/system/attachments/#{filename}") + end +end +World(AttachmentHelpers) + +When /^I modify my attachment definition to:$/ do |definition| + content = in_current_dir { File.read("app/models/user.rb") } + content.gsub!(/has_attached_file.+end/m, <<-FILE) + #{definition} + end + FILE + + write_file "app/models/user.rb", content + in_current_dir { FileUtils.rm_rf ".rbx" } +end + +When /^I upload the fixture "([^"]*)"$/ do |filename| + run_simple %(bundle exec #{runner_command} "User.create!(:attachment => File.open('#{fixture_path(filename)}'))") +end + +Then /^the attachment "([^"]*)" should have a dimension of (\d+x\d+)$/ do |filename, dimension| + in_current_dir do + geometry = `identify -format "%wx%h" "#{attachment_path(filename)}"`.strip + geometry.should == dimension + end +end + +Then /^the attachment "([^"]*)" should exist$/ do |filename| + in_current_dir do + File.exists?(attachment_path(filename)).should be + end +end + +When /^I swap the attachment "([^"]*)" with the fixture "([^"]*)"$/ do |attachment_filename, fixture_filename| + in_current_dir do + require 'fileutils' + FileUtils.rm_f attachment_path(attachment_filename) + FileUtils.cp fixture_path(fixture_filename), attachment_path(attachment_filename) + end +end + +Then /^the attachment should have the same content type as the fixture "([^"]*)"$/ do |filename| + in_current_dir do + require 'mime/types' + attachment_content_type = `bundle exec #{runner_command} "puts User.last.attachment_content_type"`.strip + attachment_content_type.should == MIME::Types.type_for(filename).first.content_type + end +end + +Then /^the attachment should have the same file name as the fixture "([^"]*)"$/ do |filename| + in_current_dir do + attachment_file_name = `bundle exec #{runner_command} "puts User.last.attachment_file_name"`.strip + attachment_file_name.should == File.name(fixture_path(filename)).to_s + end +end + +Then /^the attachment should have the same file size as the fixture "([^"]*)"$/ do |filename| + in_current_dir do + attachment_file_size = `bundle exec #{runner_command} "puts User.last.attachment_file_size"`.strip + attachment_file_size.should == File.size(fixture_path(filename)).to_s + end +end + +Then /^the attachment file "([^"]*)" should (not )?exist$/ do |filename, not_exist| + in_current_dir do + check_file_presence([attachment_path(filename)], !not_exist) + end +end + +Then /^I should have attachment columns for "([^"]*)"$/ do |attachment_name| + in_current_dir do + columns = eval(`bundle exec #{runner_command} "puts User.columns.map{ |column| [column.name, column.type] }.inspect"`.strip) + expect_columns = [ + ["#{attachment_name}_file_name", :string], + ["#{attachment_name}_content_type", :string], + ["#{attachment_name}_file_size", :integer], + ["#{attachment_name}_updated_at", :datetime] + ] + + expect_columns.all?{ |column| columns.include? column }.should be_true + end +end + +Then /^I should not have attachment columns for "([^"]*)"$/ do |attachment_name| + in_current_dir do + columns = eval(`bundle exec #{runner_command} "puts User.columns.map{ |column| [column.name, column.type] }.inspect"`.strip) + expect_columns = [ + ["#{attachment_name}_file_name", :string], + ["#{attachment_name}_content_type", :string], + ["#{attachment_name}_file_size", :integer], + ["#{attachment_name}_updated_at", :datetime] + ] + + expect_columns.none?{ |column| columns.include? column }.should be_true + end +end diff --git a/vendor/gems/paperclip/features/step_definitions/html_steps.rb b/vendor/gems/paperclip/features/step_definitions/html_steps.rb new file mode 100644 index 0000000..aac19ae --- /dev/null +++ b/vendor/gems/paperclip/features/step_definitions/html_steps.rb @@ -0,0 +1,15 @@ +Then %r{I should see an image with a path of "([^"]*)"} do |path| + page.should have_css("img[src^='#{path}']") +end + +Then %r{^the file at "([^"]*)" is the same as "([^"]*)"$} do |web_file, path| + expected = IO.read(path) + actual = if web_file.match %r{^https?://} + Net::HTTP.get(URI.parse(web_file)) + else + visit(web_file) + page.body + end + actual.force_encoding("UTF-8") if actual.respond_to?(:force_encoding) + actual.should == expected +end diff --git a/vendor/gems/paperclip/features/step_definitions/rails_steps.rb b/vendor/gems/paperclip/features/step_definitions/rails_steps.rb new file mode 100644 index 0000000..3dfbbe5 --- /dev/null +++ b/vendor/gems/paperclip/features/step_definitions/rails_steps.rb @@ -0,0 +1,138 @@ +Given /^I generate a new rails application$/ do + steps %{ + When I run `bundle exec #{new_application_command} #{APP_NAME} --skip-bundle` + And I cd to "#{APP_NAME}" + And I turn off class caching + And I write to "Gemfile" with: + """ + source "http://rubygems.org" + gem "rails", "#{framework_version}" + gem "sqlite3", :platform => :ruby + gem "activerecord-jdbcsqlite3-adapter", :platform => :jruby + gem "jruby-openssl", :platform => :jruby + gem "capybara" + gem "gherkin" + gem "aws-sdk" + """ + And I configure the application to use "paperclip" from this project + And I reset Bundler environment variable + And I successfully run `bundle install --local` + } +end + +Given /^I run a rails generator to generate a "([^"]*)" scaffold with "([^"]*)"$/ do |model_name, attributes| + step %[I successfully run `bundle exec #{generator_command} scaffold #{model_name} #{attributes}`] +end + +Given /^I run a paperclip generator to add a paperclip "([^"]*)" to the "([^"]*)" model$/ do |attachment_name, model_name| + step %[I successfully run `bundle exec #{generator_command} paperclip #{model_name} #{attachment_name}`] +end + +Given /^I run a migration$/ do + step %[I successfully run `bundle exec rake db:migrate --trace`] +end + +When /^I rollback a migration$/ do + step %[I successfully run `bundle exec rake db:rollback STEPS=1 --trace`] +end + +Given /^I update my new user view to include the file upload field$/ do + steps %{ + Given I overwrite "app/views/users/new.html.erb" with: + """ + <%= form_for @user, :html => { :multipart => true } do |f| %> + <%= f.label :name %> + <%= f.text_field :name %> + <%= f.label :attachment %> + <%= f.file_field :attachment %> + <%= submit_tag "Submit" %> + <% end %> + """ + } +end + +Given /^I update my user view to include the attachment$/ do + steps %{ + Given I overwrite "app/views/users/show.html.erb" with: + """ +

Name: <%= @user.name %>

+

Attachment: <%= image_tag @user.attachment.url %>

+ """ + } +end + +Given /^I add this snippet to the User model:$/ do |snippet| + file_name = "app/models/user.rb" + in_current_dir do + content = File.read(file_name) + File.open(file_name, 'w') { |f| f << content.sub(/end\Z/, "#{snippet}\nend") } + end +end + +Given /^I add this snippet to config\/application.rb:$/ do |snippet| + file_name = "config/application.rb" + in_current_dir do + content = File.read(file_name) + File.open(file_name, 'w') {|f| f << content.sub(/class Application < Rails::Application.*$/, "class Application < Rails::Application\n#{snippet}\n")} + end +end + +Given /^I start the rails application$/ do + in_current_dir do + require "./config/environment" + require "capybara/rails" + end +end + +Given /^I reload my application$/ do + Rails::Application.reload! +end + +When /^I turn off class caching$/ do + in_current_dir do + file = "config/environments/test.rb" + config = IO.read(file) + config.gsub!(%r{^\s*config.cache_classes.*$}, + "config.cache_classes = false") + File.open(file, "w"){|f| f.write(config) } + end +end + +Then /^the file at "([^"]*)" should be the same as "([^"]*)"$/ do |web_file, path| + expected = IO.read(path) + actual = if web_file.match %r{^https?://} + Net::HTTP.get(URI.parse(web_file)) + else + visit(web_file) + page.source + end + actual.force_encoding("UTF-8") if actual.respond_to?(:force_encoding) + actual.should == expected +end + +When /^I configure the application to use "([^\"]+)" from this project$/ do |name| + append_to_gemfile "gem '#{name}', :path => '#{PROJECT_ROOT}'" + steps %{And I run `bundle install --local`} +end + +When /^I configure the application to use "([^\"]+)"$/ do |gem_name| + append_to_gemfile "gem '#{gem_name}'" +end + +When /^I append gems from Appraisal Gemfile$/ do + File.read(ENV['BUNDLE_GEMFILE']).split(/\n/).each do |line| + if line =~ /^gem "(?!rails|appraisal)/ + append_to_gemfile line.strip + end + end +end + +When /^I comment out the gem "([^"]*)" from the Gemfile$/ do |gemname| + comment_out_gem_in_gemfile gemname +end + +Given /^I am using Rails newer than ([\d\.]+)$/ do |version| + if framework_version < version + pending "Not supported in Rails < #{version}" + end +end diff --git a/vendor/gems/paperclip/features/step_definitions/s3_steps.rb b/vendor/gems/paperclip/features/step_definitions/s3_steps.rb new file mode 100644 index 0000000..71340cc --- /dev/null +++ b/vendor/gems/paperclip/features/step_definitions/s3_steps.rb @@ -0,0 +1,14 @@ +When /^I attach the file "([^"]*)" to "([^"]*)" on S3$/ do |file_path, field| + definition = User.attachment_definitions[field.downcase.to_sym] + path = "https://paperclip.s3.amazonaws.com#{definition[:path]}" + path.gsub!(':filename', File.basename(file_path)) + path.gsub!(/:([^\/\.]+)/) do |match| + "([^\/\.]+)" + end + FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK") + step "I attach the file \"#{file_path}\" to \"#{field}\"" +end + +Then /^the file at "([^"]*)" should be uploaded to S3$/ do |url| + FakeWeb.registered_uri?(:put, url) +end diff --git a/vendor/gems/paperclip/features/step_definitions/web_steps.rb b/vendor/gems/paperclip/features/step_definitions/web_steps.rb new file mode 100644 index 0000000..5615538 --- /dev/null +++ b/vendor/gems/paperclip/features/step_definitions/web_steps.rb @@ -0,0 +1,209 @@ +# TL;DR: YOU SHOULD DELETE THIS FILE +# +# This file was generated by Cucumber-Rails and is only here to get you a head start +# These step definitions are thin wrappers around the Capybara/Webrat API that lets you +# visit pages, interact with widgets and make assertions about page content. +# +# If you use these step definitions as basis for your features you will quickly end up +# with features that are: +# +# * Hard to maintain +# * Verbose to read +# +# A much better approach is to write your own higher level step definitions, following +# the advice in the following blog posts: +# +# * http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html +# * http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/ +# * http://elabs.se/blog/15-you-re-cuking-it-wrong +# + + +require 'uri' +require 'cgi' +require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths")) +require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "selectors")) + +module WithinHelpers + def with_scope(locator) + locator ? within(*selector_for(locator)) { yield } : yield + end +end +World(WithinHelpers) + +# Single-line step scoper +When /^(.*) within (.*[^:])$/ do |step, parent| + with_scope(parent) { When step } +end + +# Multi-line step scoper +When /^(.*) within (.*[^:]):$/ do |step, parent, table_or_string| + with_scope(parent) { When "#{step}:", table_or_string } +end + +Given /^(?:|I )am on (.+)$/ do |page_name| + visit path_to(page_name) +end + +When /^(?:|I )go to (.+)$/ do |page_name| + visit path_to(page_name) +end + +When /^(?:|I )press "([^"]*)"$/ do |button| + click_button(button) +end + +When /^(?:|I )follow "([^"]*)"$/ do |link| + click_link(link) +end + +When /^(?:|I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value| + fill_in(field, :with => value) +end + +When /^(?:|I )fill in "([^"]*)" for "([^"]*)"$/ do |value, field| + fill_in(field, :with => value) +end + +# Use this to fill in an entire form with data from a table. Example: +# +# When I fill in the following: +# | Account Number | 5002 | +# | Expiry date | 2009-11-01 | +# | Note | Nice guy | +# | Wants Email? | | +# +# TODO: Add support for checkbox, select og option +# based on naming conventions. +# +When /^(?:|I )fill in the following:$/ do |fields| + fields.rows_hash.each do |name, value| + When %{I fill in "#{name}" with "#{value}"} + end +end + +When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field| + select(value, :from => field) +end + +When /^(?:|I )check "([^"]*)"$/ do |field| + check(field) +end + +When /^(?:|I )uncheck "([^"]*)"$/ do |field| + uncheck(field) +end + +When /^(?:|I )choose "([^"]*)"$/ do |field| + choose(field) +end + +When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field| + attach_file(field, File.expand_path(path)) +end + +Then /^(?:|I )should see "([^"]*)"$/ do |text| + if page.respond_to? :should + page.should have_content(text) + else + assert page.has_content?(text) + end +end + +Then /^(?:|I )should see \/([^\/]*)\/$/ do |regexp| + regexp = Regexp.new(regexp) + + if page.respond_to? :should + page.should have_xpath('//*', :text => regexp) + else + assert page.has_xpath?('//*', :text => regexp) + end +end + +Then /^(?:|I )should not see "([^"]*)"$/ do |text| + if page.respond_to? :should + page.should have_no_content(text) + else + assert page.has_no_content?(text) + end +end + +Then /^(?:|I )should not see \/([^\/]*)\/$/ do |regexp| + regexp = Regexp.new(regexp) + + if page.respond_to? :should + page.should have_no_xpath('//*', :text => regexp) + else + assert page.has_no_xpath?('//*', :text => regexp) + end +end + +Then /^the "([^"]*)" field(?: within (.*))? should contain "([^"]*)"$/ do |field, parent, value| + with_scope(parent) do + field = find_field(field) + if field.value.respond_to? :should + field.value.should =~ /#{value}/ + else + assert_match(/#{value}/, field.value) + end + end +end + +Then /^the "([^"]*)" field(?: within (.*))? should not contain "([^"]*)"$/ do |field, parent, value| + with_scope(parent) do + field = find_field(field) + if field.value.respond_to? :should_not + field.value.should_not =~ /#{value}/ + else + assert_no_match(/#{value}/, field.value) + end + end +end + +Then /^the "([^"]*)" checkbox(?: within (.*))? should be checked$/ do |label, parent| + with_scope(parent) do + field_checked = find_field(label)['checked'] + if field_checked.respond_to? :should + field_checked.should be_true + else + assert field_checked + end + end +end + +Then /^the "([^"]*)" checkbox(?: within (.*))? should not be checked$/ do |label, parent| + with_scope(parent) do + field_checked = find_field(label)['checked'] + if field_checked.respond_to? :should + field_checked.should be_false + else + assert !field_checked + end + end +end + +Then /^(?:|I )should be on (.+)$/ do |page_name| + current_path = URI.parse(current_url).path + if current_path.respond_to? :should + current_path.should == path_to(page_name) + else + assert_equal path_to(page_name), current_path + end +end + +Then /^(?:|I )should have the following query string:$/ do |expected_pairs| + query = URI.parse(current_url).query + actual_params = query ? CGI.parse(query) : {} + expected_params = {} + expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')} + + if actual_params.respond_to? :should + actual_params.should == expected_params + else + assert_equal expected_params, actual_params + end +end + +Then /^show me the page$/ do + save_and_open_page +end diff --git a/vendor/gems/paperclip/features/support/env.rb b/vendor/gems/paperclip/features/support/env.rb new file mode 100644 index 0000000..d5ac0eb --- /dev/null +++ b/vendor/gems/paperclip/features/support/env.rb @@ -0,0 +1,11 @@ +require 'aruba/cucumber' +require 'capybara/cucumber' +require 'test/unit/assertions' + +$CUCUMBER=1 + +World(Test::Unit::Assertions) + +Before do + @aruba_timeout_seconds = 120 +end diff --git a/vendor/gems/paperclip/features/support/fakeweb.rb b/vendor/gems/paperclip/features/support/fakeweb.rb new file mode 100644 index 0000000..004551a --- /dev/null +++ b/vendor/gems/paperclip/features/support/fakeweb.rb @@ -0,0 +1,10 @@ +require 'fake_web' + +FakeWeb.allow_net_connect = false + +module FakeWeb + class StubSocket + def read_timeout=(ignored) + end + end +end diff --git a/vendor/gems/paperclip/features/support/file_helpers.rb b/vendor/gems/paperclip/features/support/file_helpers.rb new file mode 100644 index 0000000..c86c96d --- /dev/null +++ b/vendor/gems/paperclip/features/support/file_helpers.rb @@ -0,0 +1,24 @@ +module FileHelpers + def append_to(path, contents) + in_current_dir do + File.open(path, "a") do |file| + file.puts + file.puts contents + end + end + end + + def append_to_gemfile(contents) + append_to('Gemfile', contents) + end + + def comment_out_gem_in_gemfile(gemname) + in_current_dir do + gemfile = File.read("Gemfile") + gemfile.sub!(/^(\s*)(gem\s*['"]#{gemname})/, "\\1# \\2") + File.open("Gemfile", 'w'){ |file| file.write(gemfile) } + end + end +end + +World(FileHelpers) diff --git a/vendor/gems/paperclip/features/support/fixtures/boot_config.txt b/vendor/gems/paperclip/features/support/fixtures/boot_config.txt new file mode 100644 index 0000000..b9a491d --- /dev/null +++ b/vendor/gems/paperclip/features/support/fixtures/boot_config.txt @@ -0,0 +1,15 @@ +class Rails::Boot + def run + load_initializer + + Rails::Initializer.class_eval do + def load_gems + @bundler_loaded ||= Bundler.require :default, Rails.env + end + end + + Rails::Initializer.run(:set_load_path) + end +end + +Rails.boot! diff --git a/vendor/gems/paperclip/features/support/fixtures/gemfile.txt b/vendor/gems/paperclip/features/support/fixtures/gemfile.txt new file mode 100644 index 0000000..44525a2 --- /dev/null +++ b/vendor/gems/paperclip/features/support/fixtures/gemfile.txt @@ -0,0 +1,5 @@ +source "http://rubygems.org" + +gem "rails", "RAILS_VERSION" +gem "rdoc" +gem "sqlite3" diff --git a/vendor/gems/paperclip/features/support/fixtures/preinitializer.txt b/vendor/gems/paperclip/features/support/fixtures/preinitializer.txt new file mode 100644 index 0000000..3ad0241 --- /dev/null +++ b/vendor/gems/paperclip/features/support/fixtures/preinitializer.txt @@ -0,0 +1,20 @@ +begin + require "rubygems" + require "bundler" +rescue LoadError + raise "Could not load the bundler gem. Install it with `gem install bundler`." +end + +if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24") + raise RuntimeError, "Your bundler version is too old for Rails 2.3." + + "Run `gem install bundler` to upgrade." +end + +begin + # Set up load paths for all bundled gems + ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__) + Bundler.setup +rescue Bundler::GemNotFound + raise RuntimeError, "Bundler couldn't find some gems." + + "Did you run `bundle install`?" +end diff --git a/vendor/gems/paperclip/features/support/paths.rb b/vendor/gems/paperclip/features/support/paths.rb new file mode 100644 index 0000000..198d3f6 --- /dev/null +++ b/vendor/gems/paperclip/features/support/paths.rb @@ -0,0 +1,28 @@ +module NavigationHelpers + # Maps a name to a path. Used by the + # + # When /^I go to (.+)$/ do |page_name| + # + # step definition in web_steps.rb + # + def path_to(page_name) + case page_name + + when /the home\s?page/ + '/' + when /the new user page/ + '/users/new' + else + begin + page_name =~ /the (.*) page/ + path_components = $1.split(/\s+/) + self.send(path_components.push('path').join('_').to_sym) + rescue Object => e + raise "Can't find mapping from \"#{page_name}\" to a path.\n" + + "Now, go and add a mapping in #{__FILE__}" + end + end + end +end + +World(NavigationHelpers) diff --git a/vendor/gems/paperclip/features/support/rails.rb b/vendor/gems/paperclip/features/support/rails.rb new file mode 100644 index 0000000..5f0e196 --- /dev/null +++ b/vendor/gems/paperclip/features/support/rails.rb @@ -0,0 +1,46 @@ +PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze +APP_NAME = 'testapp'.freeze +BUNDLE_ENV_VARS = %w(RUBYOPT BUNDLE_PATH BUNDLE_BIN_PATH BUNDLE_GEMFILE) +ORIGINAL_BUNDLE_VARS = Hash[ENV.select{ |key,value| BUNDLE_ENV_VARS.include?(key) }] + +ENV['RAILS_ENV'] = 'test' + +Before do + ENV['BUNDLE_GEMFILE'] = File.join(Dir.pwd, ENV['BUNDLE_GEMFILE']) unless ENV['BUNDLE_GEMFILE'].start_with?(Dir.pwd) + @framework_version = nil +end + +After do + ORIGINAL_BUNDLE_VARS.each_pair do |key, value| + ENV[key] = value + end +end + +When /^I reset Bundler environment variable$/ do + BUNDLE_ENV_VARS.each do |key| + ENV[key] = nil + end +end + +module RailsCommandHelpers + def framework_version?(version_string) + framework_version =~ /^#{version_string}/ + end + + def framework_version + @framework_version ||= `rails -v`[/^Rails (.+)$/, 1] + end + + def new_application_command + "rails new" + end + + def generator_command + "script/rails generate" + end + + def runner_command + "script/rails runner" + end +end +World(RailsCommandHelpers) diff --git a/vendor/gems/paperclip/features/support/selectors.rb b/vendor/gems/paperclip/features/support/selectors.rb new file mode 100644 index 0000000..76ff973 --- /dev/null +++ b/vendor/gems/paperclip/features/support/selectors.rb @@ -0,0 +1,19 @@ +module HtmlSelectorsHelpers + # Maps a name to a selector. Used primarily by the + # + # When /^(.+) within (.+)$/ do |step, scope| + # + # step definitions in web_steps.rb + # + def selector_for(locator) + case locator + when "the page" + "html > body" + else + raise "Can't find mapping from \"#{locator}\" to a selector.\n" + + "Now, go and add a mapping in #{__FILE__}" + end + end +end + +World(HtmlSelectorsHelpers) diff --git a/vendor/gems/paperclip/gemfiles/3.0.gemfile b/vendor/gems/paperclip/gemfiles/3.0.gemfile new file mode 100644 index 0000000..f317735 --- /dev/null +++ b/vendor/gems/paperclip/gemfiles/3.0.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal + +source "http://rubygems.org" + +gem "jruby-openssl", :platform=>:jruby +gem "activerecord-jdbcsqlite3-adapter", :platform=>:jruby +gem "sqlite3", :platform=>:ruby +gem "rails", "~> 3.0.15" +gem "paperclip", :path=>"../" + +gemspec :path=>"../" \ No newline at end of file diff --git a/vendor/gems/paperclip/gemfiles/3.1.gemfile b/vendor/gems/paperclip/gemfiles/3.1.gemfile new file mode 100644 index 0000000..90a3f45 --- /dev/null +++ b/vendor/gems/paperclip/gemfiles/3.1.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal + +source "http://rubygems.org" + +gem "jruby-openssl", :platform=>:jruby +gem "activerecord-jdbcsqlite3-adapter", :platform=>:jruby +gem "sqlite3", :platform=>:ruby +gem "rails", "~> 3.1.6" +gem "paperclip", :path=>"../" + +gemspec :path=>"../" \ No newline at end of file diff --git a/vendor/gems/paperclip/gemfiles/3.2.gemfile b/vendor/gems/paperclip/gemfiles/3.2.gemfile new file mode 100644 index 0000000..8eaad15 --- /dev/null +++ b/vendor/gems/paperclip/gemfiles/3.2.gemfile @@ -0,0 +1,11 @@ +# This file was generated by Appraisal + +source "http://rubygems.org" + +gem "jruby-openssl", :platform=>:jruby +gem "activerecord-jdbcsqlite3-adapter", :platform=>:jruby +gem "sqlite3", :platform=>:ruby +gem "rails", "~> 3.2.6" +gem "paperclip", :path=>"../" + +gemspec :path=>"../" \ No newline at end of file diff --git a/vendor/gems/paperclip/lib/generators/paperclip/USAGE b/vendor/gems/paperclip/lib/generators/paperclip/USAGE new file mode 100644 index 0000000..a19ba5e --- /dev/null +++ b/vendor/gems/paperclip/lib/generators/paperclip/USAGE @@ -0,0 +1,8 @@ +Description: + Explain the generator + +Example: + rails generate paperclip Thing + + This will create: + what/will/it/create diff --git a/vendor/gems/paperclip/lib/generators/paperclip/paperclip_generator.rb b/vendor/gems/paperclip/lib/generators/paperclip/paperclip_generator.rb new file mode 100644 index 0000000..3803381 --- /dev/null +++ b/vendor/gems/paperclip/lib/generators/paperclip/paperclip_generator.rb @@ -0,0 +1,32 @@ +require 'rails/generators/active_record' + +class PaperclipGenerator < ActiveRecord::Generators::Base + desc "Create a migration to add paperclip-specific fields to your model. " + + "The NAME argument is the name of your model, and the following " + + "arguments are the name of the attachments" + + argument :attachment_names, :required => true, :type => :array, :desc => "The names of the attachment(s) to add.", + :banner => "attachment_one attachment_two attachment_three ..." + + def self.source_root + @source_root ||= File.expand_path('../templates', __FILE__) + end + + def generate_migration + migration_template "paperclip_migration.rb.erb", "db/migrate/#{migration_file_name}" + end + + protected + + def migration_name + "add_attachment_#{attachment_names.join("_")}_to_#{name.underscore.pluralize}" + end + + def migration_file_name + "#{migration_name}.rb" + end + + def migration_class_name + migration_name.camelize + end +end diff --git a/vendor/gems/paperclip/lib/generators/paperclip/templates/paperclip_migration.rb.erb b/vendor/gems/paperclip/lib/generators/paperclip/templates/paperclip_migration.rb.erb new file mode 100644 index 0000000..db7d4e8 --- /dev/null +++ b/vendor/gems/paperclip/lib/generators/paperclip/templates/paperclip_migration.rb.erb @@ -0,0 +1,15 @@ +class <%= migration_class_name %> < ActiveRecord::Migration + def self.up + change_table :<%= table_name %> do |t| +<% attachment_names.each do |attachment| -%> + t.has_attached_file :<%= attachment %> +<% end -%> + end + end + + def self.down +<% attachment_names.each do |attachment| -%> + drop_attached_file :<%= table_name %>, :<%= attachment %> +<% end -%> + end +end diff --git a/vendor/gems/paperclip/lib/paperclip.rb b/vendor/gems/paperclip/lib/paperclip.rb new file mode 100644 index 0000000..dc619b7 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip.rb @@ -0,0 +1,226 @@ +# Paperclip allows file attachments that are stored in the filesystem. All graphical +# transformations are done using the Graphics/ImageMagick command line utilities and +# are stored in Tempfiles until the record is saved. Paperclip does not require a +# separate model for storing the attachment's information, instead adding a few simple +# columns to your table. +# +# Author:: Jon Yurek +# Copyright:: Copyright (c) 2008-2011 thoughtbot, inc. +# License:: MIT License (http://www.opensource.org/licenses/mit-license.php) +# +# Paperclip defines an attachment as any file, though it makes special considerations +# for image files. You can declare that a model has an attached file with the +# +has_attached_file+ method: +# +# class User < ActiveRecord::Base +# has_attached_file :avatar, :styles => { :thumb => "100x100" } +# end +# +# user = User.new +# user.avatar = params[:user][:avatar] +# user.avatar.url +# # => "/users/avatars/4/original_me.jpg" +# user.avatar.url(:thumb) +# # => "/users/avatars/4/thumb_me.jpg" +# +# See the +has_attached_file+ documentation for more details. + +require 'erb' +require 'digest' +require 'tempfile' +require 'paperclip/version' +require 'paperclip/geometry' +require 'paperclip/processor' +require 'paperclip/tempfile' +require 'paperclip/thumbnail' +require 'paperclip/interpolations' +require 'paperclip/tempfile_factory' +require 'paperclip/style' +require 'paperclip/attachment' +require 'paperclip/attachment_options' +require 'paperclip/storage' +require 'paperclip/callbacks' +require 'paperclip/content_type_detector' +require 'paperclip/glue' +require 'paperclip/errors' +require 'paperclip/missing_attachment_styles' +require 'paperclip/validators' +require 'paperclip/instance_methods' +require 'paperclip/logger' +require 'paperclip/helpers' +require 'mime/types' +require 'logger' +require 'cocaine' + +require 'paperclip/railtie' if defined?(Rails) + +# The base module that gets included in ActiveRecord::Base. See the +# documentation for Paperclip::ClassMethods for more useful information. +module Paperclip + extend Helpers + extend Logger + extend ProcessorHelpers + + # Provides configurability to Paperclip. The options available are: + # * whiny: Will raise an error if Paperclip cannot process thumbnails of + # an uploaded image. Defaults to true. + # * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors + # log levels, etc. Defaults to true. + # * command_path: Defines the path at which to find the command line + # programs if they are not visible to Rails the system's search path. Defaults to + # nil, which uses the first executable found in the user's search path. + def self.options + @options ||= { + :whiny => true, + :image_magick_path => nil, + :command_path => nil, + :log => true, + :log_command => true, + :swallow_stderr => true + } + end + + def self.io_adapters=(new_registry) + @io_adapters = new_registry + end + + def self.io_adapters + @io_adapters ||= Paperclip::AdapterRegistry.new + end + + module ClassMethods + # +has_attached_file+ gives the class it is called on an attribute that maps to a file. This + # is typically a file stored somewhere on the filesystem and has been uploaded by a user. + # The attribute returns a Paperclip::Attachment object which handles the management of + # that file. The intent is to make the attachment as much like a normal attribute. The + # thumbnails will be created when the new file is assigned, but they will *not* be saved + # until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is + # called on it, the attachment will *not* be deleted until +save+ is called. See the + # Paperclip::Attachment documentation for more specifics. There are a number of options + # you can set to change the behavior of a Paperclip attachment: + # * +url+: The full URL of where the attachment is publically accessible. This can just + # as easily point to a directory served directly through Apache as it can to an action + # that can control permissions. You can specify the full domain and path, but usually + # just an absolute path is sufficient. The leading slash *must* be included manually for + # absolute paths. The default value is + # "/system/:class/:attachment/:id_partition/:style/:filename". See + # Paperclip::Attachment#interpolate for more information on variable interpolaton. + # :url => "/:class/:attachment/:id/:style_:filename" + # :url => "http://some.other.host/stuff/:class/:id_:extension" + # * +default_url+: The URL that will be returned if there is no attachment assigned. + # This field is interpolated just as the url is. The default value is + # "/:attachment/:style/missing.png" + # has_attached_file :avatar, :default_url => "/images/default_:style_avatar.png" + # User.new.avatar_url(:small) # => "/images/default_small_avatar.png" + # * +styles+: A hash of thumbnail styles and their geometries. You can find more about + # geometry strings at the ImageMagick website + # (http://www.imagemagick.org/script/command-line-options.php#resize). Paperclip + # also adds the "#" option (e.g. "50x50#"), which will resize the image to fit maximally + # inside the dimensions and then crop the rest off (weighted at the center). The + # default value is to generate no thumbnails. + # * +default_style+: The thumbnail style that will be used by default URLs. + # Defaults to +original+. + # has_attached_file :avatar, :styles => { :normal => "100x100#" }, + # :default_style => :normal + # user.avatar.url # => "/avatars/23/normal_me.png" + # * +keep_old_files+: Keep the existing attachment files (original + resized) from + # being automatically deleted when an attachment is cleared or updated. + # Defaults to +false+.# + # * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due + # to a command line error. This will override the global setting for this attachment. + # Defaults to true. + # * +convert_options+: When creating thumbnails, use this free-form options + # array to pass in various convert command options. Typical options are "-strip" to + # remove all Exif data from the image (save space for thumbnails and avatars) or + # "-depth 8" to specify the bit depth of the resulting conversion. See ImageMagick + # convert documentation for more options: (http://www.imagemagick.org/script/convert.php) + # Note that this option takes a hash of options, each of which correspond to the style + # of thumbnail being generated. You can also specify :all as a key, which will apply + # to all of the thumbnails being generated. If you specify options for the :original, + # it would be best if you did not specify destructive options, as the intent of keeping + # the original around is to regenerate all the thumbnails when requirements change. + # has_attached_file :avatar, :styles => { :large => "300x300", :negative => "100x100" } + # :convert_options => { + # :all => "-strip", + # :negative => "-negate" + # } + # NOTE: While not deprecated yet, it is not recommended to specify options this way. + # It is recommended that :convert_options option be included in the hash passed to each + # :styles for compatibility with future versions. + # NOTE: Strings supplied to :convert_options are split on space in order to undergo + # shell quoting for safety. If your options require a space, please pre-split them + # and pass an array to :convert_options instead. + # * +storage+: Chooses the storage backend where the files will be stored. The current + # choices are :filesystem, :fog and :s3. The default is :filesystem. Make sure you read the + # documentation for Paperclip::Storage::Filesystem, Paperclip::Storage::Fog and Paperclip::Storage::S3 + # for backend-specific options. + # + # It's also possible for you to dynamically define your interpolation string for :url, + # :default_url, and :path in your model by passing a method name as a symbol as a argument + # for your has_attached_file definition: + # + # class Person + # has_attached_file :avatar, :default_url => :default_url_by_gender + # + # private + # + # def default_url_by_gender + # "/assets/avatars/default_#{gender}.png" + # end + # end + def has_attached_file(name, options = {}) + include InstanceMethods + + if attachment_definitions.nil? + self.attachment_definitions = {} + else + self.attachment_definitions = self.attachment_definitions.dup + end + + attachment_definitions[name] = Paperclip::AttachmentOptions.new(options) + Paperclip.classes_with_attachments << self.name + Paperclip.check_for_url_clash(name,attachment_definitions[name][:url],self.name) + + after_save :save_attached_files + before_destroy :prepare_for_destroy + after_destroy :destroy_attached_files + + define_paperclip_callbacks :post_process, :"#{name}_post_process" + + define_method name do |*args| + a = attachment_for(name) + (args.length > 0) ? a.to_s(args.first) : a + end + + define_method "#{name}=" do |file| + attachment_for(name).assign(file) + end + + define_method "#{name}?" do + attachment_for(name).file? + end + + validates_each(name) do |record, attr, value| + attachment = record.attachment_for(name) + attachment.send(:flush_errors) + end + end + + # Returns the attachment definitions defined by each call to + # has_attached_file. + def attachment_definitions + self.attachment_definitions + end + end +end + +# This stuff needs to be run after Paperclip is defined. +require 'paperclip/io_adapters/registry' +require 'paperclip/io_adapters/abstract_adapter' +require 'paperclip/io_adapters/identity_adapter' +require 'paperclip/io_adapters/file_adapter' +require 'paperclip/io_adapters/stringio_adapter' +require 'paperclip/io_adapters/nil_adapter' +require 'paperclip/io_adapters/attachment_adapter' +require 'paperclip/io_adapters/uploaded_file_adapter' +require 'paperclip/io_adapters/uri_adapter' diff --git a/vendor/gems/paperclip/lib/paperclip/attachment.rb b/vendor/gems/paperclip/lib/paperclip/attachment.rb new file mode 100644 index 0000000..f1720c0 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/attachment.rb @@ -0,0 +1,478 @@ +# encoding: utf-8 +require 'uri' +require 'paperclip/url_generator' + +module Paperclip + # The Attachment class manages the files for a given attachment. It saves + # when the model saves, deletes when the model is destroyed, and processes + # the file upon assignment. + class Attachment + def self.default_options + @default_options ||= { + :convert_options => {}, + :default_style => :original, + :default_url => "/:attachment/:style/missing.png", + :restricted_characters => /[&$+,\/:;=?@<>\[\]\{\}\|\\\^~%# ]/, + :hash_data => ":class/:attachment/:id/:style/:updated_at", + :hash_digest => "SHA1", + :interpolator => Paperclip::Interpolations, + :only_process => [], + :path => ":rails_root/public:url", + :preserve_files => false, + :processors => [:thumbnail], + :source_file_options => {}, + :storage => :filesystem, + :styles => {}, + :url => "/system/:class/:attachment/:id_partition/:style/:filename", + :url_generator => Paperclip::UrlGenerator, + :use_default_time_zone => true, + :use_timestamp => true, + :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails] + } + end + + attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny, + :options, :interpolator, :source_file_options, :whiny + attr_accessor :post_processing + + # Creates an Attachment object. +name+ is the name of the attachment, + # +instance+ is the ActiveRecord object instance it's attached to, and + # +options+ is the same as the hash passed to +has_attached_file+. + # + # Options include: + # + # +url+ - a relative URL of the attachment. This is interpolated using +interpolator+ + # +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+ + # +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details + # +only_process+ - style args to be run through the post-processor. This defaults to the empty list + # +default_url+ - a URL for the missing image + # +default_style+ - the style to use when don't specify an argument to e.g. #url, #path + # +storage+ - the storage mechanism. Defaults to :filesystem + # +use_timestamp+ - whether to append an anti-caching timestamp to image URLs. Defaults to true + # +whiny+, +whiny_thumbnails+ - whether to raise when thumbnailing fails + # +use_default_time_zone+ - related to +use_timestamp+. Defaults to true + # +hash_digest+ - a string representing a class that will be used to hash URLs for obfuscation + # +hash_data+ - the relative URL for the hash data. This is interpolated using +interpolator+ + # +hash_secret+ - a secret passed to the +hash_digest+ + # +convert_options+ - flags passed to the +convert+ command for processing + # +source_file_options+ - flags passed to the +convert+ command that controls how the file is read + # +processors+ - classes that transform the attachment. Defaults to [:thumbnail] + # +preserve_files+ - whether to keep files on the filesystem when deleting to clearing the attachment. Defaults to false + # +interpolator+ - the object used to interpolate filenames and URLs. Defaults to Paperclip::Interpolations + # +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator + def initialize(name, instance, options = {}) + @name = name + @instance = instance + + options = self.class.default_options.merge(options) + + @options = options + @post_processing = true + @queued_for_delete = [] + @queued_for_write = {} + @errors = {} + @dirty = false + @interpolator = options[:interpolator] + @url_generator = options[:url_generator].new(self, @options) + @source_file_options = options[:source_file_options] + @whiny = options[:whiny] + + initialize_storage + end + + # What gets called when you call instance.attachment = File. It clears + # errors, assigns attributes, and processes the file. It + # also queues up the previous file for deletion, to be flushed away on + # #save of its host. In addition to form uploads, you can also assign + # another Paperclip attachment: + # new_user.avatar = old_user.avatar + def assign uploaded_file + ensure_required_accessors! + file = Paperclip.io_adapters.for(uploaded_file) + + @options[:only_process].map!(&:to_sym) + self.clear(*@options[:only_process]) + return nil if file.nil? + + @queued_for_write[:original] = file + instance_write(:file_name, cleanup_filename(file.original_filename)) + instance_write(:content_type, file.content_type.to_s.strip) + instance_write(:file_size, file.size) + instance_write(:fingerprint, file.fingerprint) if instance_respond_to?(:fingerprint) + instance_write(:created_at, Time.now) if has_enabled_but_unset_created_at? + instance_write(:updated_at, Time.now) + + @dirty = true + + post_process(*@options[:only_process]) if post_processing + + # Reset the file size if the original file was reprocessed. + instance_write(:file_size, @queued_for_write[:original].size) + instance_write(:fingerprint, @queued_for_write[:original].fingerprint) if instance_respond_to?(:fingerprint) + end + + # Returns the public URL of the attachment with a given style. This does + # not necessarily need to point to a file that your Web server can access + # and can instead point to an action in your app, for example for fine grained + # security; this has a serious performance tradeoff. + # + # Options: + # + # +timestamp+ - Add a timestamp to the end of the URL. Default: true. + # +escape+ - Perform URI escaping to the URL. Default: true. + # + # Global controls (set on has_attached_file): + # + # +interpolator+ - The object that fills in a URL pattern's variables. + # +default_url+ - The image to show when the attachment has no image. + # +url+ - The URL for a saved image. + # +url_generator+ - The object that generates a URL. Default: Paperclip::UrlGenerator. + # + # As mentioned just above, the object that generates this URL can be passed + # in, for finer control. This object must respond to two methods: + # + # +#new(Paperclip::Attachment, options_hash)+ + # +#for(style_name, options_hash)+ + def url(style_name = default_style, options = {}) + default_options = {:timestamp => @options[:use_timestamp], :escape => true} + + if options == true || options == false # Backwards compatibility. + @url_generator.for(style_name, default_options.merge(:timestamp => options)) + else + @url_generator.for(style_name, default_options.merge(options)) + end + end + + # Returns the path of the attachment as defined by the :path option. If the + # file is stored in the filesystem the path refers to the path of the file + # on disk. If the file is stored in S3, the path is the "key" part of the + # URL, and the :bucket option refers to the S3 bucket. + def path(style_name = default_style) + path = original_filename.nil? ? nil : interpolate(path_option, style_name) + path.respond_to?(:unescape) ? path.unescape : path + end + + # Alias to +url+ + def to_s style_name = default_style + url(style_name) + end + + def default_style + @options[:default_style] + end + + def styles + if @options[:styles].respond_to?(:call) || @normalized_styles.nil? + styles = @options[:styles] + styles = styles.call(self) if styles.respond_to?(:call) + + @normalized_styles = styles.dup + @normalized_styles.each_pair do |name, options| + @normalized_styles[name.to_sym] = Paperclip::Style.new(name.to_sym, options.dup, self) + end + end + @normalized_styles + end + + def processors + processing_option = @options[:processors] + + if processing_option.respond_to?(:call) + processing_option.call(instance) + else + processing_option + end + end + + # Returns an array containing the errors on this attachment. + def errors + @errors + end + + # Returns true if there are changes that need to be saved. + def dirty? + @dirty + end + + # Saves the file, if there are no errors. If there are, it flushes them to + # the instance's errors and returns false, cancelling the save. + def save + flush_deletes unless @options[:keep_old_files] + flush_writes + @dirty = false + true + end + + # Clears out the attachment. Has the same effect as previously assigning + # nil to the attachment. Does NOT save. If you wish to clear AND save, + # use #destroy. + def clear(*styles_to_clear) + if styles_to_clear.any? + queue_some_for_delete(*styles_to_clear) + else + queue_all_for_delete + @queued_for_write = {} + @errors = {} + end + end + + # Destroys the attachment. Has the same effect as previously assigning + # nil to the attachment *and saving*. This is permanent. If you wish to + # wipe out the existing attachment but not save, use #clear. + def destroy + unless @options[:preserve_files] + clear + save + end + end + + # Returns the uploaded file if present. + def uploaded_file + instance_read(:uploaded_file) + end + + # Returns the name of the file as originally assigned, and lives in the + # _file_name attribute of the model. + def original_filename + instance_read(:file_name) + end + + # Returns the size of the file as originally assigned, and lives in the + # _file_size attribute of the model. + def size + instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size) + end + + # Returns the fingerprint of the file, if one's defined. The fingerprint is + # stored in the _fingerpring attribute of the model. + def fingerprint + instance_read(:fingerprint) + end + + # Returns the content_type of the file as originally assigned, and lives + # in the _content_type attribute of the model. + def content_type + instance_read(:content_type) + end + + # Returns the creation time of the file as originally assigned, and + # lives in the _created_at attribute of the model. + def created_at + if able_to_store_created_at? + time = instance_read(:created_at) + time && time.to_f.to_i + end + end + + # Returns the last modified time of the file as originally assigned, and + # lives in the _updated_at attribute of the model. + def updated_at + time = instance_read(:updated_at) + time && time.to_f.to_i + end + + # The time zone to use for timestamp interpolation. Using the default + # time zone ensures that results are consistent across all threads. + def time_zone + @options[:use_default_time_zone] ? Time.zone_default : Time.zone + end + + # Returns a unique hash suitable for obfuscating the URL of an otherwise + # publicly viewable attachment. + def hash_key(style_name = default_style) + raise ArgumentError, "Unable to generate hash without :hash_secret" unless @options[:hash_secret] + require 'openssl' unless defined?(OpenSSL) + data = interpolate(@options[:hash_data], style_name) + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options[:hash_digest]).new, @options[:hash_secret], data) + end + + # This method really shouldn't be called that often. It's expected use is + # in the paperclip:refresh rake task and that's it. It will regenerate all + # thumbnails forcefully, by reobtaining the original file and going through + # the post-process again. + def reprocess!(*style_args) + saved_only_process, @options[:only_process] = @options[:only_process], style_args + begin + assign(self) + save + rescue Errno::EACCES => e + warn "#{e} - skipping file." + false + ensure + @options[:only_process] = saved_only_process + end + end + + # Returns true if a file has been assigned. + def file? + !original_filename.blank? + end + + alias :present? :file? + + # Determines whether the instance responds to this attribute. Used to prevent + # calculations on fields we won't even store. + def instance_respond_to?(attr) + instance.respond_to?(:"#{name}_#{attr}") + end + + # Writes the attachment-specific attribute on the instance. For example, + # instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's + # "avatar_file_name" field (assuming the attachment is called avatar). + def instance_write(attr, value) + setter = :"#{name}_#{attr}=" + responds = instance.respond_to?(setter) + self.instance_variable_set("@_#{setter.to_s.chop}", value) + instance.send(setter, value) if responds || attr.to_s == "file_name" + end + + # Reads the attachment-specific attribute on the instance. See instance_write + # for more details. + def instance_read(attr) + getter = :"#{name}_#{attr}" + responds = instance.respond_to?(getter) + cached = self.instance_variable_get("@_#{getter}") + return cached if cached + instance.send(getter) if responds || attr.to_s == "file_name" + end + + private + + def path_option + @options[:path].respond_to?(:call) ? @options[:path].call(self) : @options[:path] + end + + def ensure_required_accessors! #:nodoc: + %w(file_name).each do |field| + unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=") + raise Paperclip::Error.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'") + end + end + end + + def log message #:nodoc: + Paperclip.log(message) + end + + def valid_assignment? file #:nodoc: + file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type)) + end + + def initialize_storage #:nodoc: + storage_class_name = @options[:storage].to_s.downcase.camelize + begin + storage_module = Paperclip::Storage.const_get(storage_class_name) + rescue NameError + raise Errors::StorageMethodNotFound, "Cannot load storage module '#{storage_class_name}'" + end + self.extend(storage_module) + end + + def extra_options_for(style) #:nodoc: + all_options = @options[:convert_options][:all] + all_options = all_options.call(instance) if all_options.respond_to?(:call) + style_options = @options[:convert_options][style] + style_options = style_options.call(instance) if style_options.respond_to?(:call) + + [ style_options, all_options ].compact.join(" ") + end + + def extra_source_file_options_for(style) #:nodoc: + all_options = @options[:source_file_options][:all] + all_options = all_options.call(instance) if all_options.respond_to?(:call) + style_options = @options[:source_file_options][style] + style_options = style_options.call(instance) if style_options.respond_to?(:call) + + [ style_options, all_options ].compact.join(" ") + end + + def post_process(*style_args) #:nodoc: + return if @queued_for_write[:original].nil? + + instance.run_paperclip_callbacks(:post_process) do + instance.run_paperclip_callbacks(:"#{name}_post_process") do + post_process_styles(*style_args) + end + end + end + + def post_process_styles(*style_args) #:nodoc: + post_process_style(:original, styles[:original]) if styles.include?(:original) && process_style?(:original, style_args) + styles.reject{ |name, style| name == :original }.each do |name, style| + post_process_style(name, style) if process_style?(name, style_args) + end + end + + def post_process_style(name, style) #:nodoc: + begin + raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank? + @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor| + Paperclip.processor(processor).make(file, style.processor_options, self) + end + @queued_for_write[name] = Paperclip.io_adapters.for(@queued_for_write[name]) + rescue Paperclip::Error => e + log("An error was received while processing: #{e.inspect}") + (@errors[:processing] ||= []) << e.message if @options[:whiny] + end + end + + def process_style?(style_name, style_args) #:nodoc: + style_args.empty? || style_args.include?(style_name) + end + + def interpolate(pattern, style_name = default_style) #:nodoc: + interpolator.interpolate(pattern, self, style_name) + end + + def queue_some_for_delete(*styles) + @queued_for_delete += styles.uniq.map do |style| + path(style) if exists?(style) + end.compact + end + + def queue_all_for_delete #:nodoc: + return if @options[:preserve_files] || !file? + @queued_for_delete += [:original, *styles.keys].uniq.map do |style| + path(style) if exists?(style) + end.compact + instance_write(:file_name, nil) + instance_write(:content_type, nil) + instance_write(:file_size, nil) + instance_write(:fingerprint, nil) + instance_write(:created_at, nil) if has_enabled_but_unset_created_at? + instance_write(:updated_at, nil) + end + + def flush_errors #:nodoc: + @errors.each do |error, message| + [message].flatten.each {|m| instance.errors.add(name, m) } + end + end + + # called by storage after the writes are flushed and before @queued_for_writes is cleared + def after_flush_writes + @queued_for_write.each do |style, file| + file.close unless file.closed? + file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path) + end + end + + def cleanup_filename(filename) + if @options[:restricted_characters] + filename.gsub(@options[:restricted_characters], '_') + else + filename + end + end + + # Check if attachment database table has a created_at field + def able_to_store_created_at? + @instance.respond_to?("#{name}_created_at".to_sym) + end + + # Check if attachment database table has a created_at field which is not yet set + def has_enabled_but_unset_created_at? + able_to_store_created_at? && !instance_read(:created_at) + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/attachment_options.rb b/vendor/gems/paperclip/lib/paperclip/attachment_options.rb new file mode 100644 index 0000000..df592cd --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/attachment_options.rb @@ -0,0 +1,9 @@ +module Paperclip + class AttachmentOptions < Hash + def initialize(options) + options.each do |k, v| + self.[]=(k, v) + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/callbacks.rb b/vendor/gems/paperclip/lib/paperclip/callbacks.rb new file mode 100644 index 0000000..ba7f424 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/callbacks.rb @@ -0,0 +1,30 @@ +module Paperclip + module Callbacks + def self.included(base) + base.extend(Defining) + base.send(:include, Running) + end + + module Defining + def define_paperclip_callbacks(*callbacks) + define_callbacks *[callbacks, {:terminator => "result == false"}].flatten + callbacks.each do |callback| + eval <<-end_callbacks + def before_#{callback}(*args, &blk) + set_callback(:#{callback}, :before, *args, &blk) + end + def after_#{callback}(*args, &blk) + set_callback(:#{callback}, :after, *args, &blk) + end + end_callbacks + end + end + end + + module Running + def run_paperclip_callbacks(callback, &block) + run_callbacks(callback, &block) + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/content_type_detector.rb b/vendor/gems/paperclip/lib/paperclip/content_type_detector.rb new file mode 100644 index 0000000..f33f149 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/content_type_detector.rb @@ -0,0 +1,67 @@ +module Paperclip + class ContentTypeDetector + EMPTY_TYPE = "inode/x-empty" + SENSIBLE_DEFAULT = "application/octet-stream" + + def initialize(filename) + @filename = filename + end + + def detect + if blank? + SENSIBLE_DEFAULT + elsif empty? + EMPTY_TYPE + elsif !match? + type_from_file_command + elsif !multiple? + possible_types.first + else + best_type_match + end.to_s + end + + private + + def empty? + File.exists?(@filename) && File.size(@filename) == 0 + end + + def blank? + @filename.nil? || @filename.empty? + end + + def possible_types + @possible_types ||= MIME::Types.type_for(@filename) + end + + def match? + possible_types.length > 0 + end + + def multiple? + possible_types.length > 1 + end + + def best_type_match + official_types = possible_types.reject {|type| type.content_type.match(/\/x-/) } + (official_types.first || possible_types.first).content_type + end + + def type_from_file_command + type = begin + # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist. + Paperclip.run("file", "-b --mime :file", :file => @filename) + rescue Cocaine::CommandLineError => e + Paperclip.log("Error while determining content type: #{e}") + SENSIBLE_DEFAULT + end + + if type.match(/\(.*?\)/) + type = SENSIBLE_DEFAULT + end + type.split(/[:;\s]+/)[0] + end + + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/errors.rb b/vendor/gems/paperclip/lib/paperclip/errors.rb new file mode 100644 index 0000000..add598f --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/errors.rb @@ -0,0 +1,27 @@ +module Paperclip + # A base error class for Paperclip. Most of the error that will be thrown + # from Paperclip will inherits from this class. + class Error < StandardError + end + + module Errors + # Will be thrown when a storage method is not found. + class StorageMethodNotFound < Paperclip::Error + end + + # Will be thrown when a command or executable is not found. + class CommandNotFoundError < Paperclip::Error + end + + # Will be thrown when ImageMagic cannot determine the uploaded file's + # metadata, usually this would mean the file is not an image. + class NotIdentifiedByImageMagickError < Paperclip::Error + end + + # Will be thrown if the interpolation is creating an infinite loop. If you + # are creating an interpolator which might cause an infinite loop, you + # should be throwing this error upon the infinite loop as well. + class InfiniteInterpolationError < Paperclip::Error + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/geometry.rb b/vendor/gems/paperclip/lib/paperclip/geometry.rb new file mode 100644 index 0000000..dbcbc16 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/geometry.rb @@ -0,0 +1,155 @@ +module Paperclip + + # Defines the geometry of an image. + class Geometry + attr_accessor :height, :width, :modifier + + # Gives a Geometry representing the given height and width + def initialize width = nil, height = nil, modifier = nil + @height = height.to_f + @width = width.to_f + @modifier = modifier + end + + # Uses ImageMagick to determing the dimensions of a file, passed in as either a + # File or path. + # NOTE: (race cond) Do not reassign the 'file' variable inside this method as it is likely to be + # a Tempfile object, which would be eligible for file deletion when no longer referenced. + def self.from_file file + file_path = file.respond_to?(:path) ? file.path : file + raise(Errors::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")) if file_path.blank? + geometry = begin + silence_stream(STDERR) do + Paperclip.run("identify", "-format %wx%h :file", :file => "#{file_path}[0]") + end + rescue Cocaine::ExitStatusError + "" + rescue Cocaine::CommandNotFoundError => e + raise Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.") + end + parse(geometry) || + raise(Errors::NotIdentifiedByImageMagickError.new("#{file_path} is not recognized by the 'identify' command.")) + end + + # Parses a "WxH" formatted string, where W is the width and H is the height. + def self.parse string + if match = (string && string.match(/\b(\d*)x?(\d*)\b([\>\<\#\@\%^!])?/i)) + Geometry.new(*match[1,3]) + end + end + + # True if the dimensions represent a square + def square? + height == width + end + + # True if the dimensions represent a horizontal rectangle + def horizontal? + height < width + end + + # True if the dimensions represent a vertical rectangle + def vertical? + height > width + end + + # The aspect ratio of the dimensions. + def aspect + width / height + end + + # Returns the larger of the two dimensions + def larger + [height, width].max + end + + # Returns the smaller of the two dimensions + def smaller + [height, width].min + end + + # Returns the width and height in a format suitable to be passed to Geometry.parse + def to_s + s = "" + s << width.to_i.to_s if width > 0 + s << "x#{height.to_i}" if height > 0 + s << modifier.to_s + s + end + + # Same as to_s + def inspect + to_s + end + + # Returns the scaling and cropping geometries (in string-based ImageMagick format) + # neccessary to transform this Geometry into the Geometry given. If crop is true, + # then it is assumed the destination Geometry will be the exact final resolution. + # In this case, the source Geometry is scaled so that an image containing the + # destination Geometry would be completely filled by the source image, and any + # overhanging image would be cropped. Useful for square thumbnail images. The cropping + # is weighted at the center of the Geometry. + def transformation_to dst, crop = false + if crop + ratio = Geometry.new( dst.width / self.width, dst.height / self.height ) + scale_geometry, scale = scaling(dst, ratio) + crop_geometry = cropping(dst, ratio, scale) + else + scale_geometry = dst.to_s + end + + [ scale_geometry, crop_geometry ] + end + + # resize to a new geometry + # @param geometry [String] the Paperclip geometry definition to resize to + # @example + # Paperclip::Geometry.new(150, 150).resize_to('50x50!') + # #=> Paperclip::Geometry(50, 50) + def resize_to(geometry) + new_geometry = Paperclip::Geometry.parse geometry + case new_geometry.modifier + when '!', '#' + new_geometry + when '>' + if new_geometry.width >= self.width && new_geometry.height >= self.height + self + else + scale_to new_geometry + end + when '<' + if new_geometry.width <= self.width || new_geometry.height <= self.height + self + else + scale_to new_geometry + end + else + scale_to new_geometry + end + end + + private + + def scaling dst, ratio + if ratio.horizontal? || ratio.square? + [ "%dx" % dst.width, ratio.width ] + else + [ "x%d" % dst.height, ratio.height ] + end + end + + def cropping dst, ratio, scale + if ratio.horizontal? || ratio.square? + "%dx%d+%d+%d" % [ dst.width, dst.height, 0, (self.height * scale - dst.height) / 2 ] + else + "%dx%d+%d+%d" % [ dst.width, dst.height, (self.width * scale - dst.width) / 2, 0 ] + end + end + + # scale to the requested geometry and preserve the aspect ratio + def scale_to(new_geometry) + scale = [new_geometry.width.to_f / self.width.to_f , new_geometry.height.to_f / self.height.to_f].min + Paperclip::Geometry.new((self.width * scale).round, (self.height * scale).round) + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/glue.rb b/vendor/gems/paperclip/lib/paperclip/glue.rb new file mode 100644 index 0000000..b358ae5 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/glue.rb @@ -0,0 +1,18 @@ +require 'paperclip/callbacks' +require 'paperclip/validators' +require 'paperclip/schema' + +module Paperclip + module Glue + def self.included(base) + base.extend ClassMethods + base.send :include, Callbacks + base.send :include, Validators + base.send :include, Schema if defined? ActiveRecord + base.class_attribute :attachment_definitions + + locale_path = Dir.glob(File.dirname(__FILE__) + "/locales/*.{rb,yml}") + I18n.load_path += locale_path unless I18n.load_path.include?(locale_path) + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/helpers.rb b/vendor/gems/paperclip/lib/paperclip/helpers.rb new file mode 100644 index 0000000..ae602a6 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/helpers.rb @@ -0,0 +1,59 @@ +module Paperclip + module Helpers + def configure + yield(self) if block_given? + end + + def interpolates key, &block + Paperclip::Interpolations[key] = block + end + + # The run method takes the name of a binary to run, the arguments to that binary + # and some options: + # + # :command_path -> A $PATH-like variable that defines where to look for the binary + # on the filesystem. Colon-separated, just like $PATH. + # + # :expected_outcodes -> An array of integers that defines the expected exit codes + # of the binary. Defaults to [0]. + # + # :log_command -> Log the command being run when set to true (defaults to false). + # This will only log if logging in general is set to true as well. + # + # :swallow_stderr -> Set to true if you don't care what happens on STDERR. + # + def run(cmd, arguments = "", local_options = {}) + command_path = options[:command_path] + Cocaine::CommandLine.path = ( Cocaine::CommandLine.path ? [Cocaine::CommandLine.path].flatten | [command_path] : command_path ) + local_options = local_options.merge(:logger => logger) if logging? && (options[:log_command] || local_options[:log_command]) + Cocaine::CommandLine.new(cmd, arguments, local_options).run + end + + # Find all instances of the given Active Record model +klass+ with attachment +name+. + # This method is used by the refresh rake tasks. + def each_instance_with_attachment(klass, name) + class_for(klass).unscoped.where("#{name}_file_name IS NOT NULL").find_each do |instance| + yield(instance) + end + end + + def class_for(class_name) + class_name.split('::').inject(Object) do |klass, partial_class_name| + klass.const_defined?(partial_class_name) ? klass.const_get(partial_class_name, false) : klass.const_missing(partial_class_name) + end + end + + def check_for_url_clash(name,url,klass) + @names_url ||= {} + default_url = url || Attachment.default_options[:url] + if @names_url[name] && @names_url[name][:url] == default_url && @names_url[name][:class] != klass && @names_url[name][:url] !~ /:class/ + log("Duplicate URL for #{name} with #{default_url}. This will clash with attachment defined in #{@names_url[name][:class]} class") + end + @names_url[name] = {:url => default_url, :class => klass} + end + + def reset_duplicate_clash_check! + @names_url = nil + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/instance_methods.rb b/vendor/gems/paperclip/lib/paperclip/instance_methods.rb new file mode 100644 index 0000000..a1d46f1 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/instance_methods.rb @@ -0,0 +1,35 @@ +module Paperclip + module InstanceMethods #:nodoc: + def attachment_for name + @_paperclip_attachments ||= {} + @_paperclip_attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name]) + end + + def each_attachment + self.class.attachment_definitions.each do |name, definition| + yield(name, attachment_for(name)) + end + end + + def save_attached_files + Paperclip.log("Saving attachments.") + each_attachment do |name, attachment| + attachment.send(:save) + end + end + + def destroy_attached_files + Paperclip.log("Deleting attachments.") + each_attachment do |name, attachment| + attachment.send(:flush_deletes) + end + end + + def prepare_for_destroy + Paperclip.log("Scheduling attachments for deletion.") + each_attachment do |name, attachment| + attachment.send(:queue_all_for_delete) + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/interpolations.rb b/vendor/gems/paperclip/lib/paperclip/interpolations.rb new file mode 100644 index 0000000..a14b597 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/interpolations.rb @@ -0,0 +1,180 @@ +module Paperclip + # This module contains all the methods that are available for interpolation + # in paths and urls. To add your own (or override an existing one), you + # can either open this module and define it, or call the + # Paperclip.interpolates method. + module Interpolations + extend self + + # Hash assignment of interpolations. Included only for compatibility, + # and is not intended for normal use. + def self.[]= name, block + define_method(name, &block) + end + + # Hash access of interpolations. Included only for compatibility, + # and is not intended for normal use. + def self.[] name + method(name) + end + + # Returns a sorted list of all interpolations. + def self.all + self.instance_methods(false).sort + end + + # Perform the actual interpolation. Takes the pattern to interpolate + # and the arguments to pass, which are the attachment and style name. + # You can pass a method name on your record as a symbol, which should turn + # an interpolation pattern for Paperclip to use. + def self.interpolate pattern, *args + pattern = args.first.instance.send(pattern) if pattern.kind_of? Symbol + all.reverse.inject(pattern) do |result, tag| + result.gsub(/:#{tag}/) do |match| + send( tag, *args ) + end + end + end + + # Returns the filename, the same way as ":basename.:extension" would. + def filename attachment, style_name + [ basename(attachment, style_name), extension(attachment, style_name) ].reject(&:blank?).join(".") + end + + # Returns the interpolated URL. Will raise an error if the url itself + # contains ":url" to prevent infinite recursion. This interpolation + # is used in the default :path to ease default specifications. + RIGHT_HERE = "#{__FILE__.gsub(%r{^\./}, "")}:#{__LINE__ + 3}" + def url attachment, style_name + raise Errors::InfiniteInterpolationError if caller.any?{|b| b.index(RIGHT_HERE) } + attachment.url(style_name, :timestamp => false, :escape => false) + end + + # Returns the timestamp as defined by the _updated_at field + # in the server default time zone unless :use_global_time_zone is set + # to false. Note that a Rails.config.time_zone change will still + # invalidate any path or URL that uses :timestamp. For a + # time_zone-agnostic timestamp, use #updated_at. + def timestamp attachment, style_name + attachment.instance_read(:updated_at).in_time_zone(attachment.time_zone).to_s + end + + # Returns an integer timestamp that is time zone-neutral, so that paths + # remain valid even if a server's time zone changes. + def updated_at attachment, style_name + attachment.updated_at + end + + # Returns the Rails.root constant. + def rails_root attachment, style_name + Rails.root + end + + # Returns the Rails.env constant. + def rails_env attachment, style_name + Rails.env + end + + # Returns the underscored, pluralized version of the class name. + # e.g. "users" for the User class. + # NOTE: The arguments need to be optional, because some tools fetch + # all class names. Calling #class will return the expected class. + def class attachment = nil, style_name = nil + return super() if attachment.nil? && style_name.nil? + attachment.instance.class.to_s.underscore.pluralize + end + + # Returns the basename of the file. e.g. "file" for "file.jpg" + def basename attachment, style_name + attachment.original_filename.gsub(/#{Regexp.escape(File.extname(attachment.original_filename))}$/, "") + end + + # Returns the extension of the file. e.g. "jpg" for "file.jpg" + # If the style has a format defined, it will return the format instead + # of the actual extension. + def extension attachment, style_name + ((style = attachment.styles[style_name.to_s.to_sym]) && style[:format]) || + File.extname(attachment.original_filename).gsub(/^\.+/, "") + end + + # Returns an extension based on the content type. e.g. "jpeg" for + # "image/jpeg". If the style has a specified format, it will override the + # content-type detection. + # + # Each mime type generally has multiple extensions associated with it, so + # if the extension from the original filename is one of these extensions, + # that extension is used, otherwise, the first in the list is used. + def content_type_extension attachment, style_name + mime_type = MIME::Types[attachment.content_type] + extensions_for_mime_type = unless mime_type.empty? + mime_type.first.extensions + else + [] + end + + original_extension = extension(attachment, style_name) + style = attachment.styles[style_name.to_s.to_sym] + if style && style[:format] + style[:format].to_s + elsif extensions_for_mime_type.include? original_extension + original_extension + elsif !extensions_for_mime_type.empty? + extensions_for_mime_type.first + else + # It's possible, though unlikely, that the mime type is not in the + # database, so just use the part after the '/' in the mime type as the + # extension. + %r{/([^/]*)$}.match(attachment.content_type)[1] + end + end + + # Returns the id of the instance. + def id attachment, style_name + attachment.instance.id + end + + # Returns the #to_param of the instance. + def param attachment, style_name + attachment.instance.to_param + end + + # Returns the fingerprint of the instance. + def fingerprint attachment, style_name + attachment.fingerprint + end + + # Returns a the attachment hash. See Paperclip::Attachment#hash_key for + # more details. + def hash attachment=nil, style_name=nil + if attachment && style_name + attachment.hash_key(style_name) + else + super() + end + end + + # Returns the id of the instance in a split path form. e.g. returns + # 000/001/234 for an id of 1234. + def id_partition attachment, style_name + case id = attachment.instance.id + when Integer + ("%09d" % id).scan(/\d{3}/).join("/") + when String + id.scan(/.{3}/).first(3).join("/") + else + nil + end + end + + # Returns the pluralized form of the attachment name. e.g. + # "avatars" for an attachment of :avatar + def attachment attachment, style_name + attachment.name.to_s.downcase.pluralize + end + + # Returns the style, or the default style if nil is supplied. + def style attachment, style_name + style_name || attachment.default_style + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/io_adapters/abstract_adapter.rb b/vendor/gems/paperclip/lib/paperclip/io_adapters/abstract_adapter.rb new file mode 100644 index 0000000..40642e2 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/io_adapters/abstract_adapter.rb @@ -0,0 +1,31 @@ +require 'active_support/core_ext/module/delegation' + +module Paperclip + class AbstractAdapter + attr_reader :content_type, :original_filename, :size + delegate :close, :closed?, :eof?, :path, :rewind, :unlink, :to => :@tempfile + + def fingerprint + @fingerprint ||= Digest::MD5.file(path).to_s + end + + def read(length = nil, buffer = nil) + @tempfile.read(length, buffer) + end + + def inspect + "#{self.class}: #{self.original_filename}" + end + + private + + def destination + @destination ||= TempfileFactory.new.generate(original_filename) + end + + def copy_to_tempfile(src) + FileUtils.cp(src.path, destination.path) + destination + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/io_adapters/attachment_adapter.rb b/vendor/gems/paperclip/lib/paperclip/io_adapters/attachment_adapter.rb new file mode 100644 index 0000000..17c8c5e --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/io_adapters/attachment_adapter.rb @@ -0,0 +1,36 @@ +module Paperclip + class AttachmentAdapter < AbstractAdapter + def initialize(target) + @target, @style = case target + when Paperclip::Attachment + [target, :original] + when Paperclip::Style + [target.attachment, target.name] + end + + cache_current_values + end + + private + + def cache_current_values + @original_filename = @target.original_filename + @content_type = @target.content_type + @tempfile = copy_to_tempfile(@target) + @size = @tempfile.size || @target.size + end + + def copy_to_tempfile(src) + if src.respond_to? :copy_to_local_file + src.copy_to_local_file(@style, destination.path) + else + FileUtils.cp(src.path(@style), destination.path) + end + destination + end + end +end + +Paperclip.io_adapters.register Paperclip::AttachmentAdapter do |target| + Paperclip::Attachment === target || Paperclip::Style === target +end diff --git a/vendor/gems/paperclip/lib/paperclip/io_adapters/file_adapter.rb b/vendor/gems/paperclip/lib/paperclip/io_adapters/file_adapter.rb new file mode 100644 index 0000000..fc10216 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/io_adapters/file_adapter.rb @@ -0,0 +1,22 @@ +module Paperclip + class FileAdapter < AbstractAdapter + def initialize(target) + @target = target + cache_current_values + end + + private + + def cache_current_values + @original_filename = @target.original_filename if @target.respond_to?(:original_filename) + @original_filename ||= File.basename(@target.path) + @tempfile = copy_to_tempfile(@target) + @content_type = ContentTypeDetector.new(@target.path).detect + @size = File.size(@target) + end + end +end + +Paperclip.io_adapters.register Paperclip::FileAdapter do |target| + File === target || Tempfile === target +end diff --git a/vendor/gems/paperclip/lib/paperclip/io_adapters/identity_adapter.rb b/vendor/gems/paperclip/lib/paperclip/io_adapters/identity_adapter.rb new file mode 100644 index 0000000..4688413 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/io_adapters/identity_adapter.rb @@ -0,0 +1,12 @@ +module Paperclip + class IdentityAdapter < AbstractAdapter + def new(adapter) + adapter + end + end +end + +Paperclip.io_adapters.register Paperclip::IdentityAdapter.new do |target| + Paperclip.io_adapters.registered?(target) +end + diff --git a/vendor/gems/paperclip/lib/paperclip/io_adapters/nil_adapter.rb b/vendor/gems/paperclip/lib/paperclip/io_adapters/nil_adapter.rb new file mode 100644 index 0000000..e5d844c --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/io_adapters/nil_adapter.rb @@ -0,0 +1,34 @@ +module Paperclip + class NilAdapter < AbstractAdapter + def initialize(target) + end + + def original_filename + "" + end + + def content_type + "" + end + + def size + 0 + end + + def nil? + true + end + + def read(*args) + nil + end + + def eof? + true + end + end +end + +Paperclip.io_adapters.register Paperclip::NilAdapter do |target| + target.nil? || ( (Paperclip::Attachment === target) && !target.present? ) +end diff --git a/vendor/gems/paperclip/lib/paperclip/io_adapters/registry.rb b/vendor/gems/paperclip/lib/paperclip/io_adapters/registry.rb new file mode 100644 index 0000000..8ae43f9 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/io_adapters/registry.rb @@ -0,0 +1,32 @@ +module Paperclip + class AdapterRegistry + class NoHandlerError < Paperclip::Error; end + + attr_reader :registered_handlers + + def initialize + @registered_handlers = [] + end + + def register(handler_class, &block) + @registered_handlers << [block, handler_class] + end + + def handler_for(target) + @registered_handlers.each do |tester, handler| + return handler if tester.call(target) + end + raise NoHandlerError.new("No handler found for #{target.inspect}") + end + + def registered?(target) + @registered_handlers.any? do |tester, handler| + handler === target + end + end + + def for(target) + handler_for(target).new(target) + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/io_adapters/stringio_adapter.rb b/vendor/gems/paperclip/lib/paperclip/io_adapters/stringio_adapter.rb new file mode 100644 index 0000000..bebcfc4 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/io_adapters/stringio_adapter.rb @@ -0,0 +1,36 @@ +module Paperclip + class StringioAdapter < AbstractAdapter + def initialize(target) + @target = target + cache_current_values + @tempfile = copy_to_tempfile(@target) + end + + attr_writer :original_filename, :content_type + private + + def cache_current_values + @original_filename = @target.original_filename if @target.respond_to?(:original_filename) + @original_filename ||= "stringio.txt" + @original_filename = @original_filename.strip + + @content_type = @target.content_type if @target.respond_to?(:content_type) + @content_type ||= "text/plain" + + @size = @target.size + end + + def copy_to_tempfile(src) + while data = src.read(16*1024) + destination.write(data) + end + destination.rewind + destination + end + + end +end + +Paperclip.io_adapters.register Paperclip::StringioAdapter do |target| + StringIO === target +end diff --git a/vendor/gems/paperclip/lib/paperclip/io_adapters/uploaded_file_adapter.rb b/vendor/gems/paperclip/lib/paperclip/io_adapters/uploaded_file_adapter.rb new file mode 100644 index 0000000..84d9418 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/io_adapters/uploaded_file_adapter.rb @@ -0,0 +1,26 @@ +module Paperclip + class UploadedFileAdapter < AbstractAdapter + def initialize(target) + @target = target + cache_current_values + + if @target.respond_to?(:tempfile) + @tempfile = copy_to_tempfile(@target.tempfile) + else + @tempfile = copy_to_tempfile(@target) + end + end + + private + + def cache_current_values + @original_filename = @target.original_filename + @content_type = @target.content_type + @size = File.size(@target.path) + end + end +end + +Paperclip.io_adapters.register Paperclip::UploadedFileAdapter do |target| + target.class.name.include?("UploadedFile") +end diff --git a/vendor/gems/paperclip/lib/paperclip/io_adapters/uri_adapter.rb b/vendor/gems/paperclip/lib/paperclip/io_adapters/uri_adapter.rb new file mode 100644 index 0000000..3bf2eb8 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/io_adapters/uri_adapter.rb @@ -0,0 +1,42 @@ +require 'open-uri' + +module Paperclip + class UriAdapter < AbstractAdapter + def initialize(target) + @target = target + @content = download_content + cache_current_values + @tempfile = copy_to_tempfile(@content) + end + + attr_writer :original_filename, :content_type + private + + def download_content + open(@target) + end + + def cache_current_values + @original_filename = @target.path.split("/").last + @original_filename ||= "index.html" + @original_filename = @original_filename.strip + + @content_type = @content.content_type if @content.respond_to?(:content_type) + @content_type ||= "text/html" + + @size = @content.size + end + + def copy_to_tempfile(src) + while data = src.read(16*1024) + destination.write(data) + end + destination.rewind + destination + end + end +end + +Paperclip.io_adapters.register Paperclip::UriAdapter do |target| + target.kind_of?(URI) +end diff --git a/vendor/gems/paperclip/lib/paperclip/locales/en.yml b/vendor/gems/paperclip/lib/paperclip/locales/en.yml new file mode 100644 index 0000000..d203815 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/locales/en.yml @@ -0,0 +1,17 @@ +en: + errors: + messages: + in_between: "must be in between %{min} and %{max}" + + number: + human: + storage_units: + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" diff --git a/vendor/gems/paperclip/lib/paperclip/logger.rb b/vendor/gems/paperclip/lib/paperclip/logger.rb new file mode 100644 index 0000000..fe3a20b --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/logger.rb @@ -0,0 +1,21 @@ +module Paperclip + module Logger + # Log a paperclip-specific line. This will log to STDOUT + # by default. Set Paperclip.options[:log] to false to turn off. + def log message + logger.info("[paperclip] #{message}") if logging? + end + + def logger #:nodoc: + @logger ||= options[:logger] || ::Logger.new(STDOUT) + end + + def logger=(logger) + @logger = logger + end + + def logging? #:nodoc: + options[:log] + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/matchers.rb b/vendor/gems/paperclip/lib/paperclip/matchers.rb new file mode 100644 index 0000000..16992b1 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/matchers.rb @@ -0,0 +1,64 @@ +require 'paperclip/matchers/have_attached_file_matcher' +require 'paperclip/matchers/validate_attachment_presence_matcher' +require 'paperclip/matchers/validate_attachment_content_type_matcher' +require 'paperclip/matchers/validate_attachment_size_matcher' + +module Paperclip + module Shoulda + # Provides RSpec-compatible & Test::Unit-compatible matchers for testing Paperclip attachments. + # + # *RSpec* + # + # In spec_helper.rb, you'll need to require the matchers: + # + # require "paperclip/matchers" + # + # And _include_ the module: + # + # Spec::Runner.configure do |config| + # config.include Paperclip::Shoulda::Matchers + # end + # + # Example: + # describe User do + # it { should have_attached_file(:avatar) } + # it { should validate_attachment_presence(:avatar) } + # it { should validate_attachment_content_type(:avatar). + # allowing('image/png', 'image/gif'). + # rejecting('text/plain', 'text/xml') } + # it { should validate_attachment_size(:avatar). + # less_than(2.megabytes) } + # end + # + # + # *TestUnit* + # + # In test_helper.rb, you'll need to require the matchers as well: + # + # require "paperclip/matchers" + # + # And _extend_ the module: + # + # class ActiveSupport::TestCase + # extend Paperclip::Shoulda::Matchers + # + # #...other initializers...# + # end + # + # Example: + # require 'test_helper' + # + # class UserTest < ActiveSupport::TestCase + # should have_attached_file(:avatar) + # should validate_attachment_presence(:avatar) + # should validate_attachment_content_type(:avatar). + # allowing('image/png', 'image/gif'). + # rejecting('text/plain', 'text/xml') + # should validate_attachment_size(:avatar). + # less_than(2.megabytes) + # end + # + module Matchers + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb b/vendor/gems/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb new file mode 100644 index 0000000..d3c1957 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb @@ -0,0 +1,57 @@ +module Paperclip + module Shoulda + module Matchers + # Ensures that the given instance or class has an attachment with the + # given name. + # + # Example: + # describe User do + # it { should have_attached_file(:avatar) } + # end + def have_attached_file name + HaveAttachedFileMatcher.new(name) + end + + class HaveAttachedFileMatcher + def initialize attachment_name + @attachment_name = attachment_name + end + + def matches? subject + @subject = subject + @subject = @subject.class unless Class === @subject + responds? && has_column? && included? + end + + def failure_message + "Should have an attachment named #{@attachment_name}" + end + + def negative_failure_message + "Should not have an attachment named #{@attachment_name}" + end + + def description + "have an attachment named #{@attachment_name}" + end + + protected + + def responds? + methods = @subject.instance_methods.map(&:to_s) + methods.include?("#{@attachment_name}") && + methods.include?("#{@attachment_name}=") && + methods.include?("#{@attachment_name}?") + end + + def has_column? + @subject.column_names.include?("#{@attachment_name}_file_name") + end + + def included? + @subject.ancestors.include?(Paperclip::InstanceMethods) + end + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb b/vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb new file mode 100644 index 0000000..ff7d234 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb @@ -0,0 +1,100 @@ +module Paperclip + module Shoulda + module Matchers + # Ensures that the given instance or class validates the content type of + # the given attachment as specified. + # + # Example: + # describe User do + # it { should validate_attachment_content_type(:icon). + # allowing('image/png', 'image/gif'). + # rejecting('text/plain', 'text/xml') } + # end + def validate_attachment_content_type name + ValidateAttachmentContentTypeMatcher.new(name) + end + + class ValidateAttachmentContentTypeMatcher + def initialize attachment_name + @attachment_name = attachment_name + @allowed_types = [] + @rejected_types = [] + end + + def allowing *types + @allowed_types = types.flatten + self + end + + def rejecting *types + @rejected_types = types.flatten + self + end + + def matches? subject + @subject = subject + @subject = @subject.new if @subject.class == Class + @allowed_types && @rejected_types && + allowed_types_allowed? && rejected_types_rejected? + end + + def failure_message + "#{expected_attachment}\n".tap do |message| + message << accepted_types_and_failures + message << "\n\n" if @allowed_types.present? && @rejected_types.present? + message << rejected_types_and_failures + end + end + + def description + "validate the content types allowed on attachment #{@attachment_name}" + end + + protected + + def accepted_types_and_failures + if @allowed_types.present? + "Accept content types: #{@allowed_types.join(", ")}\n".tap do |message| + if @missing_allowed_types.any? + message << " #{@missing_allowed_types.join(", ")} were rejected." + else + message << " All were accepted successfully." + end + end + end + end + def rejected_types_and_failures + if @rejected_types.present? + "Reject content types: #{@rejected_types.join(", ")}\n".tap do |message| + if @missing_rejected_types.any? + message << " #{@missing_rejected_types.join(", ")} were accepted." + else + message << " All were rejected successfully." + end + end + end + end + + def expected_attachment + "Expected #{@attachment_name}:\n" + end + + def type_allowed?(type) + @subject.send("#{@attachment_name}_content_type=", type) + @subject.valid? + @subject.errors[:"#{@attachment_name}_content_type"].blank? + end + + def allowed_types_allowed? + @missing_allowed_types ||= @allowed_types.reject { |type| type_allowed?(type) } + @missing_allowed_types.none? + end + + def rejected_types_rejected? + @missing_rejected_types ||= @rejected_types.select { |type| type_allowed?(type) } + @missing_rejected_types.none? + end + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb b/vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb new file mode 100644 index 0000000..c241414 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb @@ -0,0 +1,54 @@ +module Paperclip + module Shoulda + module Matchers + # Ensures that the given instance or class validates the presence of the + # given attachment. + # + # describe User do + # it { should validate_attachment_presence(:avatar) } + # end + def validate_attachment_presence name + ValidateAttachmentPresenceMatcher.new(name) + end + + class ValidateAttachmentPresenceMatcher + def initialize attachment_name + @attachment_name = attachment_name + end + + def matches? subject + @subject = subject + @subject = subject.new if subject.class == Class + error_when_not_valid? && no_error_when_valid? + end + + def failure_message + "Attachment #{@attachment_name} should be required" + end + + def negative_failure_message + "Attachment #{@attachment_name} should not be required" + end + + def description + "require presence of attachment #{@attachment_name}" + end + + protected + + def error_when_not_valid? + @subject.send(@attachment_name).assign(nil) + @subject.valid? + @subject.errors[:"#{@attachment_name}"].present? + end + + def no_error_when_valid? + @file = StringIO.new(".") + @subject.send(@attachment_name).assign(@file) + @subject.valid? + @subject.errors[:"#{@attachment_name}"].blank? + end + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb b/vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb new file mode 100644 index 0000000..92988b9 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb @@ -0,0 +1,95 @@ +module Paperclip + module Shoulda + module Matchers + # Ensures that the given instance or class validates the size of the + # given attachment as specified. + # + # Examples: + # it { should validate_attachment_size(:avatar). + # less_than(2.megabytes) } + # it { should validate_attachment_size(:icon). + # greater_than(1024) } + # it { should validate_attachment_size(:icon). + # in(0..100) } + def validate_attachment_size name + ValidateAttachmentSizeMatcher.new(name) + end + + class ValidateAttachmentSizeMatcher + def initialize attachment_name + @attachment_name = attachment_name + end + + def less_than size + @high = size + self + end + + def greater_than size + @low = size + self + end + + def in range + @low, @high = range.first, range.last + self + end + + def matches? subject + @subject = subject + @subject = @subject.new if @subject.class == Class + lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high? + end + + def failure_message + "Attachment #{@attachment_name} must be between #{@low} and #{@high} bytes" + end + + def negative_failure_message + "Attachment #{@attachment_name} cannot be between #{@low} and #{@high} bytes" + end + + def description + "validate the size of attachment #{@attachment_name}" + end + + protected + + def override_method object, method, &replacement + (class << object; self; end).class_eval do + define_method(method, &replacement) + end + end + + def passes_validation_with_size(new_size) + file = StringIO.new(".") + override_method(file, :size){ new_size } + override_method(file, :to_tempfile){ file } + + @subject.send(@attachment_name).post_processing = false + @subject.send(@attachment_name).assign(file) + @subject.valid? + @subject.errors[:"#{@attachment_name}_file_size"].blank? + ensure + @subject.send(@attachment_name).post_processing = true + end + + def lower_than_low? + @low.nil? || !passes_validation_with_size(@low - 1) + end + + def higher_than_low? + @low.nil? || passes_validation_with_size(@low + 1) + end + + def lower_than_high? + @high.nil? || @high == Float::INFINITY || passes_validation_with_size(@high - 1) + end + + def higher_than_high? + @high.nil? || @high == Float::INFINITY || !passes_validation_with_size(@high + 1) + end + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/missing_attachment_styles.rb b/vendor/gems/paperclip/lib/paperclip/missing_attachment_styles.rb new file mode 100644 index 0000000..989e159 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/missing_attachment_styles.rb @@ -0,0 +1,84 @@ + +require 'set' +module Paperclip + class << self + attr_accessor :classes_with_attachments + attr_writer :registered_attachments_styles_path + def registered_attachments_styles_path + @registered_attachments_styles_path ||= Rails.root.join('public/system/paperclip_attachments.yml').to_s + end + end + + self.classes_with_attachments = Set.new + + # Get list of styles saved on previous deploy (running rake paperclip:refresh:missing_styles) + def self.get_registered_attachments_styles + YAML.load_file(Paperclip.registered_attachments_styles_path) + rescue Errno::ENOENT + nil + end + private_class_method :get_registered_attachments_styles + + def self.save_current_attachments_styles! + File.open(Paperclip.registered_attachments_styles_path, 'w') do |f| + YAML.dump(current_attachments_styles, f) + end + end + + # Returns hash with styles for all classes using Paperclip. + # Unfortunately current version does not work with lambda styles:( + # { + # :User => {:avatar => [:small, :big]}, + # :Book => { + # :cover => [:thumb, :croppable]}, + # :sample => [:thumb, :big]}, + # } + # } + def self.current_attachments_styles + Hash.new.tap do |current_styles| + Paperclip.classes_with_attachments.each do |klass_name| + klass = Paperclip.class_for(klass_name) + klass.attachment_definitions.each do |attachment_name, attachment_attributes| + # TODO: is it even possible to take into account Procs? + next if attachment_attributes[:styles].kind_of?(Proc) + attachment_attributes[:styles].try(:keys).try(:each) do |style_name| + klass_sym = klass.to_s.to_sym + current_styles[klass_sym] ||= Hash.new + current_styles[klass_sym][attachment_name.to_sym] ||= Array.new + current_styles[klass_sym][attachment_name.to_sym] << style_name.to_sym + current_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq! + end + end + end + end + end + private_class_method :current_attachments_styles + + # Returns hash with styles missing from recent run of rake paperclip:refresh:missing_styles + # { + # :User => {:avatar => [:big]}, + # :Book => { + # :cover => [:croppable]}, + # } + # } + def self.missing_attachments_styles + current_styles = current_attachments_styles + registered_styles = get_registered_attachments_styles + + Hash.new.tap do |missing_styles| + current_styles.each do |klass, attachment_definitions| + attachment_definitions.each do |attachment_name, styles| + registered = registered_styles[klass][attachment_name] || [] rescue [] + missed = styles - registered + if missed.present? + klass_sym = klass.to_s.to_sym + missing_styles[klass_sym] ||= Hash.new + missing_styles[klass_sym][attachment_name.to_sym] ||= Array.new + missing_styles[klass_sym][attachment_name.to_sym].concat(missed.to_a) + missing_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq! + end + end + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/processor.rb b/vendor/gems/paperclip/lib/paperclip/processor.rb new file mode 100644 index 0000000..7d55005 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/processor.rb @@ -0,0 +1,85 @@ +module Paperclip + # Paperclip processors allow you to modify attached files when they are + # attached in any way you are able. Paperclip itself uses command-line + # programs for its included Thumbnail processor, but custom processors + # are not required to follow suit. + # + # Processors are required to be defined inside the Paperclip module and + # are also required to be a subclass of Paperclip::Processor. There is + # only one method you *must* implement to properly be a subclass: + # #make, but #initialize may also be of use. Both methods accept 3 + # arguments: the file that will be operated on (which is an instance of + # File), a hash of options that were defined in has_attached_file's + # style hash, and the Paperclip::Attachment itself. + # + # All #make needs to return is an instance of File (Tempfile is + # acceptable) which contains the results of the processing. + # + # See Paperclip.run for more information about using command-line + # utilities from within Processors. + class Processor + attr_accessor :file, :options, :attachment + + def initialize file, options = {}, attachment = nil + @file = file + @options = options + @attachment = attachment + end + + def make + end + + def self.make file, options = {}, attachment = nil + new(file, options, attachment).make + end + + # The convert method runs the convert binary with the provided arguments. + # See Paperclip.run for the available options. + def convert(arguments = "", local_options = {}) + Paperclip.run('convert', arguments, local_options) + end + + # The identify method runs the identify binary with the provided arguments. + # See Paperclip.run for the available options. + def identify(arguments = "", local_options = {}) + Paperclip.run('identify', arguments, local_options) + end + end + + module ProcessorHelpers + def processor(name) #:nodoc: + @known_processors ||= {} + if @known_processors[name.to_s] + @known_processors[name.to_s] + else + name = name.to_s.camelize + load_processor(name) unless Paperclip.const_defined?(name) + processor = Paperclip.const_get(name) + @known_processors[name.to_s] = processor + end + end + + def load_processor(name) + if defined?(Rails.root) && Rails.root + require File.expand_path(Rails.root.join("lib", "paperclip_processors", "#{name.underscore}.rb")) + end + end + + def clear_processors! + @known_processors.try(:clear) + end + + # You can add your own processor via the Paperclip configuration. Normally + # Paperclip will load all processors from the + # Rails.root/lib/paperclip_processors directory, but here you can add any + # existing class using this mechanism. + # + # Paperclip.configure do |c| + # c.register_processor :watermarker, WatermarkingProcessor.new + # end + def register_processor(name, processor) + @known_processors ||= {} + @known_processors[name.to_s] = processor + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/railtie.rb b/vendor/gems/paperclip/lib/paperclip/railtie.rb new file mode 100644 index 0000000..42a6572 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/railtie.rb @@ -0,0 +1,31 @@ +require 'paperclip' +require 'paperclip/schema' + +module Paperclip + require 'rails' + + class Railtie < Rails::Railtie + initializer 'paperclip.insert_into_active_record' do |app| + ActiveSupport.on_load :active_record do + Paperclip::Railtie.insert + end + + if app.config.respond_to?(:paperclip_defaults) + Paperclip::Attachment.default_options.merge!(app.config.paperclip_defaults) + end + end + + rake_tasks { load "tasks/paperclip.rake" } + end + + class Railtie + def self.insert + Paperclip.options[:logger] = Rails.logger + + if defined?(ActiveRecord) + Paperclip.options[:logger] = ActiveRecord::Base.logger + ActiveRecord::Base.send(:include, Paperclip::Glue) + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/schema.rb b/vendor/gems/paperclip/lib/paperclip/schema.rb new file mode 100644 index 0000000..49813b7 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/schema.rb @@ -0,0 +1,75 @@ +require 'active_support/deprecation' + +module Paperclip + # Provides helper methods that can be used in migrations. + module Schema + COLUMNS = {:file_name => :string, + :content_type => :string, + :file_size => :integer, + :updated_at => :datetime} + + def self.included(base) + ActiveRecord::ConnectionAdapters::Table.send :include, TableDefinition + ActiveRecord::ConnectionAdapters::TableDefinition.send :include, TableDefinition + ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, Statements + + if defined?(ActiveRecord::Migration::CommandRecorder) # Rails 3.1+ + ActiveRecord::Migration::CommandRecorder.send :include, CommandRecorder + end + end + + module Statements + def add_attachment(table_name, *attachment_names) + raise ArgumentError, "Please specify attachment name in your add_attachment call in your migration." if attachment_names.empty? + + attachment_names.each do |attachment_name| + COLUMNS.each_pair do |column_name, column_type| + add_column(table_name, "#{attachment_name}_#{column_name}", column_type) + end + end + end + + def remove_attachment(table_name, *attachment_names) + raise ArgumentError, "Please specify attachment name in your remove_attachment call in your migration." if attachment_names.empty? + + attachment_names.each do |attachment_name| + COLUMNS.each_pair do |column_name, column_type| + remove_column(table_name, "#{attachment_name}_#{column_name}") + end + end + end + + def drop_attached_file(*args) + ActiveSupport::Deprecation.warn "Method `drop_attached_file` in the migration has been deprecated and will be replaced by `remove_attachment`." + remove_attachment(*args) + end + end + + module TableDefinition + def attachment(*attachment_names) + attachment_names.each do |attachment_name| + COLUMNS.each_pair do |column_name, column_type| + column("#{attachment_name}_#{column_name}", column_type) + end + end + end + + def has_attached_file(*attachment_names) + ActiveSupport::Deprecation.warn "Method `t.has_attached_file` in the migration has been deprecated and will be replaced by `t.attachment`." + attachment(*attachment_names) + end + end + + module CommandRecorder + def add_attachment(*args) + record(:add_attachment, args) + end + + private + + def invert_add_attachment(args) + [:remove_attachment, args] + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/storage.rb b/vendor/gems/paperclip/lib/paperclip/storage.rb new file mode 100644 index 0000000..f1fc672 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/storage.rb @@ -0,0 +1,3 @@ +require "paperclip/storage/filesystem" +require "paperclip/storage/fog" +require "paperclip/storage/s3" diff --git a/vendor/gems/paperclip/lib/paperclip/storage/filesystem.rb b/vendor/gems/paperclip/lib/paperclip/storage/filesystem.rb new file mode 100644 index 0000000..40d734b --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/storage/filesystem.rb @@ -0,0 +1,77 @@ +module Paperclip + module Storage + # The default place to store attachments is in the filesystem. Files on the local + # filesystem can be very easily served by Apache without requiring a hit to your app. + # They also can be processed more easily after they've been saved, as they're just + # normal files. There is one Filesystem-specific option for has_attached_file. + # * +path+: The location of the repository of attachments on disk. This can (and, in + # almost all cases, should) be coordinated with the value of the +url+ option to + # allow files to be saved into a place where Apache can serve them without + # hitting your app. Defaults to + # ":rails_root/public/:attachment/:id/:style/:basename.:extension" + # By default this places the files in the app's public directory which can be served + # directly. If you are using capistrano for deployment, a good idea would be to + # make a symlink to the capistrano-created system directory from inside your app's + # public directory. + # See Paperclip::Attachment#interpolate for more information on variable interpolaton. + # :path => "/var/app/attachments/:class/:id/:style/:basename.:extension" + module Filesystem + def self.extended base + end + + def exists?(style_name = default_style) + if original_filename + File.exist?(path(style_name)) + else + false + end + end + + def flush_writes #:nodoc: + @queued_for_write.each do |style_name, file| + FileUtils.mkdir_p(File.dirname(path(style_name))) + File.open(path(style_name), "wb") do |new_file| + while chunk = file.read(16 * 1024) + new_file.write(chunk) + end + end + FileUtils.chmod(0666&~File.umask, path(style_name)) + file.rewind + end + + after_flush_writes # allows attachment to clean up temp files + + @queued_for_write = {} + end + + def flush_deletes #:nodoc: + @queued_for_delete.each do |path| + begin + log("deleting #{path}") + FileUtils.rm(path) if File.exist?(path) + rescue Errno::ENOENT => e + # ignore file-not-found, let everything else pass + end + begin + while(true) + path = File.dirname(path) + FileUtils.rmdir(path) + break if File.exists?(path) # Ruby 1.9.2 does not raise if the removal failed. + end + rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR, Errno::EACCES + # Stop trying to remove parent directories + rescue SystemCallError => e + log("There was an unexpected error while deleting directories: #{e.class}") + # Ignore it + end + end + @queued_for_delete = [] + end + end + + def copy_to_local_file(style, local_dest_path) + FileUtils.cp(path(style), local_dest_path) + end + + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/storage/fog.rb b/vendor/gems/paperclip/lib/paperclip/storage/fog.rb new file mode 100644 index 0000000..7f57e17 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/storage/fog.rb @@ -0,0 +1,200 @@ +module Paperclip + module Storage + # fog is a modern and versatile cloud computing library for Ruby. + # Among others, it supports Amazon S3 to store your files. In + # contrast to the outdated AWS-S3 gem it is actively maintained and + # supports multiple locations. + # Amazon's S3 file hosting service is a scalable, easy place to + # store files for distribution. You can find out more about it at + # http://aws.amazon.com/s3 There are a few fog-specific options for + # has_attached_file, which will be explained using S3 as an example: + # * +fog_credentials+: Takes a Hash with your credentials. For S3, + # you can use the following format: + # aws_access_key_id: '' + # aws_secret_access_key: '' + # provider: 'AWS' + # region: 'eu-west-1' + # * +fog_directory+: This is the name of the S3 bucket that will + # store your files. Remember that the bucket must be unique across + # all of Amazon S3. If the bucket does not exist, Paperclip will + # attempt to create it. + # * +path+: This is the key under the bucket in which the file will + # be stored. The URL will be constructed from the bucket and the + # path. This is what you will want to interpolate. Keys should be + # unique, like filenames, and despite the fact that S3 (strictly + # speaking) does not support directories, you can still use a / to + # separate parts of your file name. + # * +fog_public+: (optional, defaults to true) Should the uploaded + # files be public or not? (true/false) + # * +fog_host+: (optional) The fully-qualified domain name (FQDN) + # that is the alias to the S3 domain of your bucket, e.g. + # 'http://images.example.com'. This can also be used in + # conjunction with Cloudfront (http://aws.amazon.com/cloudfront) + + module Fog + def self.extended base + begin + require 'fog' + rescue LoadError => e + e.message << " (You may need to install the fog gem)" + raise e + end unless defined?(Fog) + + base.instance_eval do + unless @options[:url].to_s.match(/^:fog.*url$/) + @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/^:rails_root\/public\/system\//, '') + @options[:url] = ':fog_public_url' + end + Paperclip.interpolates(:fog_public_url) do |attachment, style| + attachment.public_url(style) + end unless Paperclip::Interpolations.respond_to? :fog_public_url + end + end + + AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX = /^(?:[a-z]|\d(?!\d{0,2}(?:\.\d{1,3}){3}$))(?:[a-z0-9]|\.(?![\.\-])|\-(?![\.])){1,61}[a-z0-9]$/ + + def exists?(style = default_style) + if original_filename + !!directory.files.head(path(style)) + else + false + end + end + + def fog_credentials + @fog_credentials ||= parse_credentials(@options[:fog_credentials]) + end + + def fog_file + @fog_file ||= @options[:fog_file] || {} + end + + def fog_public + return @fog_public if defined?(@fog_public) + @fog_public = defined?(@options[:fog_public]) ? @options[:fog_public] : true + end + + def flush_writes + for style, file in @queued_for_write do + log("saving #{path(style)}") + retried = false + begin + directory.files.create(fog_file.merge( + :body => file, + :key => path(style), + :public => fog_public, + :content_type => file.content_type + )) + rescue Excon::Errors::NotFound + raise if retried + retried = true + directory.save + retry + ensure + file.rewind + end + end + + after_flush_writes # allows attachment to clean up temp files + + @queued_for_write = {} + end + + def flush_deletes + for path in @queued_for_delete do + log("deleting #{path}") + directory.files.new(:key => path).destroy + end + @queued_for_delete = [] + end + + def public_url(style = default_style) + if @options[:fog_host] + "#{dynamic_fog_host_for_style(style)}/#{path(style)}" + else + if fog_credentials[:provider] == 'AWS' + "https://#{host_name_for_directory}/#{path(style)}" + else + directory.files.new(:key => path(style)).public_url + end + end + end + + def expiring_url(time = (Time.now + 3600), style = default_style) + expiring_url = directory.files.get_http_url(path(style), time) + + if @options[:fog_host] + expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style)) + end + + return expiring_url + end + + def parse_credentials(creds) + creds = find_credentials(creds).stringify_keys + env = Object.const_defined?(:Rails) ? Rails.env : nil + (creds[env] || creds).symbolize_keys + end + + def copy_to_local_file(style, local_dest_path) + log("copying #{path(style)} to local file #{local_dest_path}") + local_file = ::File.open(local_dest_path, 'wb') + file = directory.files.get(path(style)) + local_file.write(file.body) + local_file.close + rescue ::Fog::Errors::Error => e + warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}") + false + end + + private + + def dynamic_fog_host_for_style(style) + if @options[:fog_host].respond_to?(:call) + @options[:fog_host].call(self) + else + (@options[:fog_host] =~ /%d/) ? @options[:fog_host] % (path(style).hash % 4) : @options[:fog_host] + end + end + + def host_name_for_directory + if @options[:fog_directory].to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX + "#{@options[:fog_directory]}.s3.amazonaws.com" + else + "s3.amazonaws.com/#{@options[:fog_directory]}" + end + end + + def find_credentials(creds) + case creds + when File + YAML::load(ERB.new(File.read(creds.path)).result) + when String, Pathname + YAML::load(ERB.new(File.read(creds)).result) + when Hash + creds + else + if creds.respond_to?(:call) + creds.call(self) + else + raise ArgumentError, "Credentials are not a path, file, hash or proc." + end + end + end + + def connection + @connection ||= ::Fog::Storage.new(fog_credentials) + end + + def directory + dir = if @options[:fog_directory].respond_to?(:call) + @options[:fog_directory].call(self) + else + @options[:fog_directory] + end + + @directory ||= connection.directories.new(:key => dir) + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/storage/s3.rb b/vendor/gems/paperclip/lib/paperclip/storage/s3.rb new file mode 100644 index 0000000..6094c75 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/storage/s3.rb @@ -0,0 +1,366 @@ +module Paperclip + module Storage + # Amazon's S3 file hosting service is a scalable, easy place to store files for + # distribution. You can find out more about it at http://aws.amazon.com/s3 + # + # To use Paperclip with S3, include the +aws-sdk+ gem in your Gemfile: + # gem 'aws-sdk' + # There are a few S3-specific options for has_attached_file: + # * +s3_credentials+: Takes a path, a File, or a Hash. The path (or File) must point + # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon + # gives you. You can 'environment-space' this just like you do to your + # database.yml file, so different environments can use different accounts: + # development: + # access_key_id: 123... + # secret_access_key: 123... + # test: + # access_key_id: abc... + # secret_access_key: abc... + # production: + # access_key_id: 456... + # secret_access_key: 456... + # This is not required, however, and the file may simply look like this: + # access_key_id: 456... + # secret_access_key: 456... + # In which case, those access keys will be used in all environments. You can also + # put your bucket name in this file, instead of adding it to the code directly. + # This is useful when you want the same account but a different bucket for + # development versus production. + # * +s3_permissions+: This is a String that should be one of the "canned" access + # policies that S3 provides (more information can be found here: + # http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAccessPolicy.html) + # The default for Paperclip is :public_read. + # + # You can set permission on a per style bases by doing the following: + # :s3_permissions => { + # :original => :private + # } + # Or globaly: + # :s3_permissions => :private + # + # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either + # 'http', 'https', or an empty string to generate scheme-less URLs. Defaults to 'http' + # when your :s3_permissions are :public_read (the default), and 'https' when your + # :s3_permissions are anything else. + # * +s3_headers+: A hash of headers or a Proc. You may specify a hash such as + # {'Expires' => 1.year.from_now.httpdate}. If you use a Proc, headers are determined at + # runtime. Paperclip will call that Proc with attachment as the only argument. + # * +bucket+: This is the name of the S3 bucket that will store your files. Remember + # that the bucket must be unique across all of Amazon S3. If the bucket does not exist + # Paperclip will attempt to create it. The bucket name will not be interpolated. + # You can define the bucket as a Proc if you want to determine it's name at runtime. + # Paperclip will call that Proc with attachment as the only argument. + # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the + # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the + # link in the +url+ entry for more information about S3 domains and buckets. + # * +url+: There are four options for the S3 url. You can choose to have the bucket's name + # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket). + # You can also specify a CNAME (which requires the CNAME to be specified as + # :s3_alias_url. You can read more about CNAMEs and S3 at + # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html + # Normally, this won't matter in the slightest and you can leave the default (which is + # path-style, or :s3_path_url). But in some cases paths don't work and you need to use + # the domain-style (:s3_domain_url). Anything else here will be treated like path-style. + # + # Notes: + # * The value of this option is a string, not a symbol. + # right: ":s3_domain_url" + # wrong: :s3_domain_url + # * If you use a CNAME for use with CloudFront, you can NOT specify https as your + # :s3_protocol; + # This is *not supported* by S3/CloudFront. Finally, when using the host + # alias, the :bucket parameter is ignored, as the hostname is used as the bucket name + # by S3. The fourth option for the S3 url is :asset_host, which uses Rails' built-in + # asset_host settings. + # * To get the full url from a paperclip'd object, use the + # image_path helper; this is what image_tag uses to generate the url for an img tag. + # * +path+: This is the key under the bucket in which the file will be stored. The + # URL will be constructed from the bucket and the path. This is what you will want + # to interpolate. Keys should be unique, like filenames, and despite the fact that + # S3 (strictly speaking) does not support directories, you can still use a / to + # separate parts of your file name. + # * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name. + # * +s3_metadata+: These key/value pairs will be stored with the + # object. This option works by prefixing each key with + # "x-amz-meta-" before sending it as a header on the object + # upload request. + # * +s3_storage_class+: If this option is set to + # :reduced_redundancy, the object will be stored using Reduced + # Redundancy Storage. RRS enables customers to reduce their + # costs by storing non-critical, reproducible data at lower + # levels of redundancy than Amazon S3's standard storage. + module S3 + def self.extended base + begin + require 'aws-sdk' + rescue LoadError => e + e.message << " (You may need to install the aws-sdk gem)" + raise e + end unless defined?(AWS::Core) + + # Overriding AWS::Core::LogFormatter to make sure it return a UTF-8 string + if AWS::VERSION >= "1.3.9" + AWS::Core::LogFormatter.class_eval do + def summarize_hash(hash) + hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',') + end + end + else + AWS::Core::ClientLogging.class_eval do + def sanitize_hash(hash) + hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',') + end + end + end + + base.instance_eval do + @s3_options = @options[:s3_options] || {} + @s3_permissions = set_permissions(@options[:s3_permissions]) + @s3_protocol = @options[:s3_protocol] || + Proc.new do |style, attachment| + permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default]) + permission = permission.call(attachment, style) if permission.respond_to?(:call) + (permission == :public_read) ? 'http' : 'https' + end + @s3_metadata = @options[:s3_metadata] || {} + @s3_headers = @options[:s3_headers] || {} + @s3_headers = @s3_headers.call(instance) if @s3_headers.respond_to?(:call) + @s3_headers = (@s3_headers).inject({}) do |headers,(name,value)| + case name.to_s + when /^x-amz-meta-(.*)/i + @s3_metadata[$1.downcase] = value + else + name = name.to_s.downcase.sub(/^x-amz-/,'').tr("-","_").to_sym + headers[name] = value + end + headers + end + + @s3_headers[:storage_class] = @options[:s3_storage_class] if @options[:s3_storage_class] + + @s3_server_side_encryption = @options[:s3_server_side_encryption] + + unless @options[:url].to_s.match(/^:s3.*url$/) || @options[:url] == ":asset_host" + @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/^:rails_root\/public\/system/, '') + @options[:url] = ":s3_path_url" + end + @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol) + + @http_proxy = @options[:http_proxy] || nil + end + + Paperclip.interpolates(:s3_alias_url) do |attachment, style| + "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}" + end unless Paperclip::Interpolations.respond_to? :s3_alias_url + Paperclip.interpolates(:s3_path_url) do |attachment, style| + "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}" + end unless Paperclip::Interpolations.respond_to? :s3_path_url + Paperclip.interpolates(:s3_domain_url) do |attachment, style| + "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{^/}, "")}" + end unless Paperclip::Interpolations.respond_to? :s3_domain_url + Paperclip.interpolates(:asset_host) do |attachment, style| + "#{attachment.path(style).gsub(%r{^/}, "")}" + end unless Paperclip::Interpolations.respond_to? :asset_host + end + + def expiring_url(time = 3600, style_name = default_style) + if path + base_options = { :expires => time, :secure => use_secure_protocol?(style_name) } + s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s + end + end + + def s3_credentials + @s3_credentials ||= parse_credentials(@options[:s3_credentials]) + end + + def s3_host_name + @options[:s3_host_name] || s3_credentials[:s3_host_name] || "s3.amazonaws.com" + end + + def s3_host_alias + @s3_host_alias = @options[:s3_host_alias] + @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.respond_to?(:call) + @s3_host_alias + end + + def s3_url_options + s3_url_options = @options[:s3_url_options] || {} + s3_url_options = s3_url_options.call(instance) if s3_url_options.respond_to?(:call) + s3_url_options + end + + def bucket_name + @bucket = @options[:bucket] || s3_credentials[:bucket] + @bucket = @bucket.call(self) if @bucket.respond_to?(:call) + @bucket or raise ArgumentError, "missing required :bucket option" + end + + def s3_interface + @s3_interface ||= begin + config = { :s3_endpoint => s3_host_name } + + if using_http_proxy? + + proxy_opts = { :host => http_proxy_host } + proxy_opts[:port] = http_proxy_port if http_proxy_port + if http_proxy_user + userinfo = http_proxy_user.to_s + userinfo += ":#{http_proxy_password}" if http_proxy_password + proxy_opts[:userinfo] = userinfo + end + config[:proxy_uri] = URI::HTTP.build(proxy_opts) + end + + [:access_key_id, :secret_access_key].each do |opt| + config[opt] = s3_credentials[opt] if s3_credentials[opt] + end + + AWS::S3.new(config.merge(@s3_options)) + end + end + + def s3_bucket + @s3_bucket ||= s3_interface.buckets[bucket_name] + end + + def s3_object style_name = default_style + s3_bucket.objects[path(style_name).sub(%r{^/},'')] + end + + def using_http_proxy? + !!@http_proxy + end + + def http_proxy_host + using_http_proxy? ? @http_proxy[:host] : nil + end + + def http_proxy_port + using_http_proxy? ? @http_proxy[:port] : nil + end + + def http_proxy_user + using_http_proxy? ? @http_proxy[:user] : nil + end + + def http_proxy_password + using_http_proxy? ? @http_proxy[:password] : nil + end + + def set_permissions permissions + permissions = { :default => permissions } unless permissions.respond_to?(:merge) + permissions.merge :default => (permissions[:default] || :public_read) + end + + def parse_credentials creds + creds = creds.respond_to?('call') ? creds.call(self) : creds + creds = find_credentials(creds).stringify_keys + env = Object.const_defined?(:Rails) ? Rails.env : nil + (creds[env] || creds).symbolize_keys + end + + def exists?(style = default_style) + if original_filename + s3_object(style).exists? + else + false + end + rescue AWS::Errors::Base => e + false + end + + def s3_permissions(style = default_style) + s3_permissions = @s3_permissions[style] || @s3_permissions[:default] + s3_permissions = s3_permissions.call(self, style) if s3_permissions.respond_to?(:call) + s3_permissions + end + + def s3_protocol(style = default_style, with_colon = false) + protocol = @s3_protocol + protocol = protocol.call(style, self) if protocol.respond_to?(:call) + + if with_colon && !protocol.empty? + "#{protocol}:" + else + protocol.to_s + end + end + + def create_bucket + s3_interface.buckets.create(bucket_name) + end + + def flush_writes #:nodoc: + @queued_for_write.each do |style, file| + begin + log("saving #{path(style)}") + acl = @s3_permissions[style] || @s3_permissions[:default] + acl = acl.call(self, style) if acl.respond_to?(:call) + write_options = { + :content_type => file.content_type, + :acl => acl + } + write_options[:metadata] = @s3_metadata unless @s3_metadata.empty? + unless @s3_server_side_encryption.blank? + write_options[:server_side_encryption] = @s3_server_side_encryption + end + write_options.merge!(@s3_headers) + s3_object(style).write(file, write_options) + rescue AWS::S3::Errors::NoSuchBucket => e + create_bucket + retry + ensure + file.rewind + end + end + + after_flush_writes # allows attachment to clean up temp files + + @queued_for_write = {} + end + + def flush_deletes #:nodoc: + @queued_for_delete.each do |path| + begin + log("deleting #{path}") + s3_bucket.objects[path.sub(%r{^/},'')].delete + rescue AWS::Errors::Base => e + # Ignore this. + end + end + @queued_for_delete = [] + end + + def copy_to_local_file(style, local_dest_path) + log("copying #{path(style)} to local file #{local_dest_path}") + local_file = ::File.open(local_dest_path, 'wb') + file = s3_object(style) + local_file.write(file.read) + local_file.close + rescue AWS::Errors::Base => e + warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}") + false + end + + private + + def find_credentials creds + case creds + when File + YAML::load(ERB.new(File.read(creds.path)).result) + when String, Pathname + YAML::load(ERB.new(File.read(creds)).result) + when Hash + creds + else + raise ArgumentError, "Credentials are not a path, file, proc, or hash." + end + end + + def use_secure_protocol?(style_name) + s3_protocol(style_name) == "https" + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/style.rb b/vendor/gems/paperclip/lib/paperclip/style.rb new file mode 100644 index 0000000..132f576 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/style.rb @@ -0,0 +1,103 @@ +# encoding: utf-8 +module Paperclip + # The Style class holds the definition of a thumbnail style, applying + # whatever processing is required to normalize the definition and delaying + # the evaluation of block parameters until useful context is available. + + class Style + + attr_reader :name, :attachment, :format + + # Creates a Style object. +name+ is the name of the attachment, + # +definition+ is the style definition from has_attached_file, which + # can be string, array or hash + def initialize name, definition, attachment + @name = name + @attachment = attachment + if definition.is_a? Hash + @geometry = definition.delete(:geometry) + @format = definition.delete(:format) + @processors = definition.delete(:processors) + @convert_options = definition.delete(:convert_options) + @source_file_options = definition.delete(:source_file_options) + @other_args = definition + elsif definition.is_a? String + @geometry = definition + @format = nil + @other_args = {} + else + @geometry, @format = [definition, nil].flatten[0..1] + @other_args = {} + end + @format = nil if @format.blank? + end + + # retrieves from the attachment the processors defined in the has_attached_file call + # (which method (in the attachment) will call any supplied procs) + # There is an important change of interface here: a style rule can set its own processors + # by default we behave as before, though. + # if a proc has been supplied, we call it here + def processors + @processors.respond_to?(:call) ? @processors.call(attachment.instance) : (@processors || attachment.processors) + end + + # retrieves from the attachment the whiny setting + def whiny + attachment.whiny + end + + # returns true if we're inclined to grumble + def whiny? + !!whiny + end + + def convert_options + @convert_options.respond_to?(:call) ? @convert_options.call(attachment.instance) : + (@convert_options || attachment.send(:extra_options_for, name)) + end + + def source_file_options + @source_file_options.respond_to?(:call) ? @source_file_options.call(attachment.instance) : + (@source_file_options || attachment.send(:extra_source_file_options_for, name)) + end + + # returns the geometry string for this style + # if a proc has been supplied, we call it here + def geometry + @geometry.respond_to?(:call) ? @geometry.call(attachment.instance) : @geometry + end + + # Supplies the hash of options that processors expect to receive as their second argument + # Arguments other than the standard geometry, format etc are just passed through from + # initialization and any procs are called here, just before post-processing. + def processor_options + args = {} + @other_args.each do |k,v| + args[k] = v.respond_to?(:call) ? v.call(attachment) : v + end + [:processors, :geometry, :format, :whiny, :convert_options, :source_file_options].each do |k| + (arg = send(k)) && args[k] = arg + end + args + end + + # Supports getting and setting style properties with hash notation to ensure backwards-compatibility + # eg. @attachment.styles[:large][:geometry]@ will still work + def [](key) + if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated, :source_file_options].include?(key) + send(key) + elsif defined? @other_args[key] + @other_args[key] + end + end + + def []=(key, value) + if [:name, :convert_options, :whiny, :processors, :geometry, :format, :animated, :source_file_options].include?(key) + send("#{key}=".intern, value) + else + @other_args[key] = value + end + end + + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/tempfile.rb b/vendor/gems/paperclip/lib/paperclip/tempfile.rb new file mode 100644 index 0000000..62ac978 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/tempfile.rb @@ -0,0 +1,43 @@ +module Paperclip + # Overriding some implementation of Tempfile + class Tempfile < ::Tempfile + # Due to how ImageMagick handles its image format conversion and how + # Tempfile handles its naming scheme, it is necessary to override how + # Tempfile makes # its names so as to allow for file extensions. Idea + # taken from the comments on this blog post: + # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions + # + # This is Ruby 1.9.3's implementation. + def make_tmpname(prefix_suffix, n) + if RUBY_PLATFORM =~ /java/ + case prefix_suffix + when String + prefix, suffix = prefix_suffix, '' + when Array + prefix, suffix = *prefix_suffix + else + raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}" + end + + t = Time.now.strftime("%y%m%d") + path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}" + else + super + end + end + end + + module TempfileEncoding + # This overrides Tempfile#binmode to make sure that the extenal encoding + # for binary mode is ASCII-8BIT. This behavior is what's in CRuby, but not + # in JRuby + def binmode + set_encoding('ASCII-8BIT') + super + end + end +end + +if RUBY_PLATFORM =~ /java/ + ::Tempfile.send :include, Paperclip::TempfileEncoding +end diff --git a/vendor/gems/paperclip/lib/paperclip/tempfile_factory.rb b/vendor/gems/paperclip/lib/paperclip/tempfile_factory.rb new file mode 100644 index 0000000..89a26a1 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/tempfile_factory.rb @@ -0,0 +1,21 @@ +module Paperclip + class TempfileFactory + + ILLEGAL_FILENAME_CHARACTERS = /^~/ + + def generate(name) + @name = name + file = Tempfile.new([basename, extension]) + file.binmode + file + end + + def extension + File.extname(@name) + end + + def basename + File.basename(@name, extension).gsub(ILLEGAL_FILENAME_CHARACTERS, '_') + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/thumbnail.rb b/vendor/gems/paperclip/lib/paperclip/thumbnail.rb new file mode 100644 index 0000000..639ebf3 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/thumbnail.rb @@ -0,0 +1,113 @@ +module Paperclip + # Handles thumbnailing images that are uploaded. + class Thumbnail < Processor + + attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options, + :source_file_options, :animated + + # List of formats that we need to preserve animation + ANIMATED_FORMATS = %w(gif) + + # Creates a Thumbnail object set to work on the +file+ given. It + # will attempt to transform the image into one defined by +target_geometry+ + # which is a "WxH"-style string. +format+ will be inferred from the +file+ + # unless specified. Thumbnail creation will raise no errors unless + # +whiny+ is true (which it is, by default. If +convert_options+ is + # set, the options will be appended to the convert command upon image conversion + # + # Options include: + # + # +geometry+ - the desired width and height of the thumbnail (required) + # +file_geometry_parser+ - an object with a method named +from_file+ that takes an image file and produces its geometry and a +transformation_to+. Defaults to Paperclip::Geometry + # +string_geometry_parser+ - an object with a method named +parse+ that takes a string and produces an object with +width+, +height+, and +to_s+ accessors. Defaults to Paperclip::Geometry + # +source_file_options+ - flags passed to the +convert+ command that influence how the source file is read + # +convert_options+ - flags passed to the +convert+ command that influence how the image is processed + # +whiny+ - whether to raise an error when processing fails. Defaults to true + # +format+ - the desired filename extension + # +animated+ - whether to merge all the layers in the image. Defaults to true + def initialize(file, options = {}, attachment = nil) + super + + geometry = options[:geometry] # this is not an option + @file = file + @crop = geometry[-1,1] == '#' + @target_geometry = (options[:string_geometry_parser] || Geometry).parse(geometry) + @current_geometry = (options[:file_geometry_parser] || Geometry).from_file(@file) + @source_file_options = options[:source_file_options] + @convert_options = options[:convert_options] + @whiny = options[:whiny].nil? ? true : options[:whiny] + @format = options[:format] + @animated = options[:animated].nil? ? true : options[:animated] + + @source_file_options = @source_file_options.split(/\s+/) if @source_file_options.respond_to?(:split) + @convert_options = @convert_options.split(/\s+/) if @convert_options.respond_to?(:split) + + @current_format = File.extname(@file.path) + @basename = File.basename(@file.path, @current_format) + end + + # Returns true if the +target_geometry+ is meant to crop. + def crop? + @crop + end + + # Returns true if the image is meant to make use of additional convert options. + def convert_options? + !@convert_options.nil? && !@convert_options.empty? + end + + # Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile + # that contains the new image. + def make + src = @file + dst = Tempfile.new([@basename, @format ? ".#{@format}" : '']) + dst.binmode + + begin + parameters = [] + parameters << source_file_options + parameters << ":source" + parameters << transformation_command + parameters << convert_options + parameters << ":dest" + + parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ") + + success = convert(parameters, :source => "#{File.expand_path(src.path)}#{'[0]' unless animated?}", :dest => File.expand_path(dst.path)) + rescue Cocaine::ExitStatusError => e + raise Paperclip::Error, "There was an error processing the thumbnail for #{@basename}" if @whiny + rescue Cocaine::CommandNotFoundError => e + raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.") + end + + dst + end + + # Returns the command ImageMagick's +convert+ needs to transform the image + # into the thumbnail. + def transformation_command + scale, crop = @current_geometry.transformation_to(@target_geometry, crop?) + trans = [] + trans << "-coalesce" if animated? + trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty? + trans << "-crop" << %["#{crop}"] << "+repage" if crop + trans + end + + protected + + # Return true if the format is animated + def animated? + @animated && (ANIMATED_FORMATS.include?(@format.to_s) || @format.blank?) && identified_as_animated? + end + + # Return true if ImageMagick's +identify+ returns an animated format + def identified_as_animated? + ANIMATED_FORMATS.include? identify("-format %m :file", :file => "#{@file.path}[0]").to_s.downcase.strip + rescue Cocaine::ExitStatusError => e + raise Paperclip::Error, "There was an error running `identify` for #{@basename}" if @whiny + rescue Cocaine::CommandNotFoundError => e + raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.") + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/url_generator.rb b/vendor/gems/paperclip/lib/paperclip/url_generator.rb new file mode 100644 index 0000000..a8b3d68 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/url_generator.rb @@ -0,0 +1,64 @@ +require 'uri' + +module Paperclip + class UrlGenerator + def initialize(attachment, attachment_options) + @attachment = attachment + @attachment_options = attachment_options + end + + def for(style_name, options) + escape_url_as_needed( + timestamp_as_needed( + @attachment_options[:interpolator].interpolate(most_appropriate_url, @attachment, style_name), + options + ), options) + end + + private + + # This method is all over the place. + def default_url + if @attachment_options[:default_url].respond_to?(:call) + @attachment_options[:default_url].call(@attachment) + elsif @attachment_options[:default_url].is_a?(Symbol) + @attachment.instance.send(@attachment_options[:default_url]) + else + @attachment_options[:default_url] + end + end + + def most_appropriate_url + if @attachment.original_filename.nil? + default_url + else + @attachment_options[:url] + end + end + + def timestamp_as_needed(url, options) + if options[:timestamp] && timestamp_possible? + delimiter_char = url.match(/\?.+=/) ? '&' : '?' + "#{url}#{delimiter_char}#{@attachment.updated_at.to_s}" + else + url + end + end + + def timestamp_possible? + @attachment.respond_to?(:updated_at) && @attachment.updated_at.present? + end + + def escape_url_as_needed(url, options) + if options[:escape] + escape_url(url) + else + url + end + end + + def escape_url(url) + (url.respond_to?(:escape) ? url.escape : URI.escape(url)).gsub(/(\/.+)\?(.+\.)/, '\1%3F\2') + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/validators.rb b/vendor/gems/paperclip/lib/paperclip/validators.rb new file mode 100644 index 0000000..b1b7d20 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/validators.rb @@ -0,0 +1,46 @@ +require 'active_model' +require 'active_support/concern' +require 'paperclip/validators/attachment_content_type_validator' +require 'paperclip/validators/attachment_presence_validator' +require 'paperclip/validators/attachment_size_validator' + +module Paperclip + module Validators + extend ActiveSupport::Concern + + included do + extend HelperMethods + include HelperMethods + end + + module ClassMethods + # This method is a shortcut to validator classes that is in + # "Attachment...Validator" format. It is almost the same thing as the + # +validates+ method that shipped with Rails, but this is customized to + # be using with attachment validators. This is helpful when you're using + # multiple attachment validators on a single attachment. + # + # Example of using the validator: + # + # validates_attachment :avatar, :presence => true, + # :content_type => { :content_type => "image/jpg" }, + # :size => { :in => 0..10.kilobytes } + # + def validates_attachment(*attributes) + options = attributes.extract_options!.dup + + Paperclip::Validators.constants.each do |constant| + if constant.to_s =~ /^Attachment(.+)Validator$/ + validator_kind = $1.underscore.to_sym + + if options.has_key?(validator_kind) + options[:"attachment_#{validator_kind}"] = options.delete(validator_kind) + end + end + end + + validates(*attributes + [options]) + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/validators/attachment_content_type_validator.rb b/vendor/gems/paperclip/lib/paperclip/validators/attachment_content_type_validator.rb new file mode 100644 index 0000000..344bb1a --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/validators/attachment_content_type_validator.rb @@ -0,0 +1,54 @@ +module Paperclip + module Validators + class AttachmentContentTypeValidator < ActiveModel::EachValidator + def initialize(options) + options[:allow_nil] = true unless options.has_key?(:allow_nil) + super + end + + def validate_each(record, attribute, value) + attribute = "#{attribute}_content_type".to_sym + value = record.send(:read_attribute_for_validation, attribute) + allowed_types = [options[:content_type]].flatten + + return if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) + + if allowed_types.none? { |type| type === value } + record.errors.add(attribute, :invalid, options.merge( + :types => allowed_types.join(', ') + )) + end + end + + def check_validity! + unless options.has_key?(:content_type) + raise ArgumentError, "You must pass in :content_type to the validator" + end + end + end + + module HelperMethods + # Places ActiveRecord-style validations on the content type of the file + # assigned. The possible options are: + # * +content_type+: Allowed content types. Can be a single content type + # or an array. Each type can be a String or a Regexp. It should be + # noted that Internet Explorer uploads files with content_types that you + # may not expect. For example, JPEG images are given image/pjpeg and + # PNGs are image/x-png, so keep that in mind when determining how you + # match. Allows all by default. + # * +message+: The message to display when the uploaded file has an invalid + # content type. + # * +if+: A lambda or name of an instance method. Validation will only + # be run is this lambda or method returns true. + # * +unless+: Same as +if+ but validates if lambda or method returns false. + # NOTE: If you do not specify an [attachment]_content_type field on your + # model, content_type validation will work _ONLY upon assignment_ and + # re-validation after the instance has been reloaded will always succeed. + # You'll still need to have a virtual attribute (created by +attr_accessor+) + # name +[attachment]_content_type+ to be able to use this validator. + def validates_attachment_content_type(*attr_names) + validates_with AttachmentContentTypeValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/validators/attachment_presence_validator.rb b/vendor/gems/paperclip/lib/paperclip/validators/attachment_presence_validator.rb new file mode 100644 index 0000000..61ec7b9 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/validators/attachment_presence_validator.rb @@ -0,0 +1,26 @@ +require 'active_model/validations/presence' + +module Paperclip + module Validators + class AttachmentPresenceValidator < ActiveModel::Validations::PresenceValidator + def validate(record) + [attributes].flatten.map do |attribute| + if record.send(:read_attribute_for_validation, "#{attribute}_file_name").blank? + record.errors.add(attribute, :blank, options) + end + end + end + end + + module HelperMethods + # Places ActiveRecord-style validations on the presence of a file. + # Options: + # * +if+: A lambda or name of an instance method. Validation will only + # be run if this lambda or method returns true. + # * +unless+: Same as +if+ but validates if lambda or method returns false. + def validates_attachment_presence(*attr_names) + validates_with AttachmentPresenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/validators/attachment_size_validator.rb b/vendor/gems/paperclip/lib/paperclip/validators/attachment_size_validator.rb new file mode 100644 index 0000000..9c8fa8c --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/validators/attachment_size_validator.rb @@ -0,0 +1,102 @@ +require 'active_model/validations/numericality' + +module Paperclip + module Validators + class AttachmentSizeValidator < ActiveModel::Validations::NumericalityValidator + AVAILABLE_CHECKS = [:less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to] + + def initialize(options) + extract_options(options) + super + end + + def validate_each(record, attr_name, value) + attr_name = "#{attr_name}_file_size".to_sym + value = record.send(:read_attribute_for_validation, attr_name) + + unless value.blank? + options.slice(*AVAILABLE_CHECKS).each do |option, option_value| + option_value = option_value.call(record) if option_value.is_a?(Proc) + option_value = extract_option_value(option, option_value) + + unless value.send(CHECKS[option], option_value) + error_message_key = options[:in] ? :in_between : option + record.errors.add(attr_name, error_message_key, filtered_options(value).merge( + :min => min_value_in_human_size(record), + :max => max_value_in_human_size(record), + :count => human_size(option_value) + )) + end + end + end + end + + def check_validity! + unless (AVAILABLE_CHECKS + [:in]).any? { |argument| options.has_key?(argument) } + raise ArgumentError, "You must pass either :less_than, :greater_than, or :in to the validator" + end + end + + private + + def extract_options(options) + if range = options[:in] + if !options[:in].respond_to?(:call) + options[:less_than_or_equal_to] = range.max + options[:greater_than_or_equal_to] = range.min + else + options[:less_than_or_equal_to] = range + options[:greater_than_or_equal_to] = range + end + end + end + + def extract_option_value(option, option_value) + if option_value.is_a?(Range) + if [:less_than, :less_than_or_equal_to].include?(option) + option_value.max + else + option_value.min + end + else + option_value + end + end + + def human_size(size) + storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true) + unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => size.to_i, :raise => true) + storage_units_format.gsub(/%n/, size.to_i.to_s).gsub(/%u/, unit).html_safe + end + + def min_value_in_human_size(record) + value = options[:greater_than_or_equal_to] || options[:greater_than] + value = value.call(record) if value.respond_to?(:call) + value = value.min if value.respond_to?(:min) + human_size(value) + end + + def max_value_in_human_size(record) + value = options[:less_than_or_equal_to] || options[:less_than] + value = value.call(record) if value.respond_to?(:call) + value = value.max if value.respond_to?(:max) + human_size(value) + end + end + + module HelperMethods + # Places ActiveRecord-style validations on the size of the file assigned. The + # possible options are: + # * +in+: a Range of bytes (i.e. +1..1.megabyte+), + # * +less_than+: equivalent to :in => 0..options[:less_than] + # * +greater_than+: equivalent to :in => options[:greater_than]..Infinity + # * +message+: error message to display, use :min and :max as replacements + # * +if+: A lambda or name of an instance method. Validation will only + # be run if this lambda or method returns true. + # * +unless+: Same as +if+ but validates if lambda or method returns false. + def validates_attachment_size(*attr_names) + validates_with AttachmentSizeValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/vendor/gems/paperclip/lib/paperclip/version.rb b/vendor/gems/paperclip/lib/paperclip/version.rb new file mode 100644 index 0000000..e4f8688 --- /dev/null +++ b/vendor/gems/paperclip/lib/paperclip/version.rb @@ -0,0 +1,3 @@ +module Paperclip + VERSION = "3.1.4" unless defined? Paperclip::VERSION +end diff --git a/vendor/gems/paperclip/lib/tasks/paperclip.rake b/vendor/gems/paperclip/lib/tasks/paperclip.rake new file mode 100644 index 0000000..4444b8e --- /dev/null +++ b/vendor/gems/paperclip/lib/tasks/paperclip.rake @@ -0,0 +1,93 @@ +module Paperclip + module Task + def self.obtain_class + class_name = ENV['CLASS'] || ENV['class'] + raise "Must specify CLASS" unless class_name + class_name + end + + def self.obtain_attachments(klass) + klass = Paperclip.class_for(klass.to_s) + name = ENV['ATTACHMENT'] || ENV['attachment'] + raise "Class #{klass.name} has no attachments specified" unless klass.respond_to?(:attachment_definitions) + if !name.blank? && klass.attachment_definitions.keys.map(&:to_s).include?(name.to_s) + [ name ] + else + klass.attachment_definitions.keys + end + end + end +end + +namespace :paperclip do + desc "Refreshes both metadata and thumbnails." + task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"] + + namespace :refresh do + desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT and STYLES splitted by comma)." + task :thumbnails => :environment do + errors = [] + klass = Paperclip::Task.obtain_class + names = Paperclip::Task.obtain_attachments(klass) + styles = (ENV['STYLES'] || ENV['styles'] || '').split(',').map(&:to_sym) + names.each do |name| + Paperclip.each_instance_with_attachment(klass, name) do |instance| + instance.send(name).reprocess!(*styles) + errors << [instance.id, instance.errors] unless instance.errors.blank? + end + end + errors.each{|e| puts "#{e.first}: #{e.last.full_messages.inspect}" } + end + + desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)." + task :metadata => :environment do + klass = Paperclip::Task.obtain_class + names = Paperclip::Task.obtain_attachments(klass) + names.each do |name| + Paperclip.each_instance_with_attachment(klass, name) do |instance| + if file = Paperclip.io_adapters.for(instance.send(name)) + instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip) + instance.send("#{name}_content_type=", file.content_type.to_s.strip) + instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size") + instance.save(:validate => false) + else + true + end + end + end + end + + desc "Regenerates missing thumbnail styles for all classes using Paperclip." + task :missing_styles => :environment do + # Force loading all model classes to never miss any has_attached_file declaration: + Dir[Rails.root + 'app/models/**/*.rb'].each { |path| load path } + Paperclip.missing_attachments_styles.each do |klass, attachment_definitions| + attachment_definitions.each do |attachment_name, missing_styles| + puts "Regenerating #{klass} -> #{attachment_name} -> #{missing_styles.inspect}" + ENV['CLASS'] = klass.to_s + ENV['ATTACHMENT'] = attachment_name.to_s + ENV['STYLES'] = missing_styles.join(',') + Rake::Task['paperclip:refresh:thumbnails'].execute + end + end + Paperclip.save_current_attachments_styles! + end + end + + desc "Cleans out invalid attachments. Useful after you've added new validations." + task :clean => :environment do + klass = Paperclip::Task.obtain_class + names = Paperclip::Task.obtain_attachments(klass) + names.each do |name| + Paperclip.each_instance_with_attachment(klass, name) do |instance| + unless instance.valid? + attributes = %w(file_size file_name content_type).map{ |suffix| "#{name}_#{suffix}".to_sym } + if attributes.any?{ |attribute| instance.errors[attribute].present? } + instance.send("#{name}=", nil) + instance.save(:validate => false) + end + end + end + end + end +end diff --git a/vendor/gems/paperclip/paperclip.gemspec b/vendor/gems/paperclip/paperclip.gemspec new file mode 100644 index 0000000..19adbf0 --- /dev/null +++ b/vendor/gems/paperclip/paperclip.gemspec @@ -0,0 +1,53 @@ +$LOAD_PATH.push File.expand_path("../lib", __FILE__) +require 'paperclip/version' + +Gem::Specification.new do |s| + s.name = "paperclip" + s.version = Paperclip::VERSION + s.platform = Gem::Platform::RUBY + s.author = "Jon Yurek" + s.email = ["jyurek@thoughtbot.com"] + s.homepage = "https://github.com/thoughtbot/paperclip" + s.summary = "File attachments as attributes for ActiveRecord" + s.description = "Easy upload management for ActiveRecord" + + s.rubyforge_project = "paperclip" + + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + s.require_paths = ["lib"] + + if File.exists?('UPGRADING') + s.post_install_message = File.read("UPGRADING") + end + + s.requirements << "ImageMagick" + s.required_ruby_version = ">= 1.9.2" + + s.add_dependency('activerecord', '>= 3.0.0') + s.add_dependency('activemodel', '>= 3.0.0') + s.add_dependency('activesupport', '>= 3.0.0') + s.add_dependency('cocaine', '>= 0.0.2') + s.add_dependency('mime-types') + + s.add_development_dependency('shoulda') + s.add_development_dependency('appraisal') + s.add_development_dependency('mocha') + s.add_development_dependency('aws-sdk') + s.add_development_dependency('bourne') + s.add_development_dependency('sqlite3', '~> 1.3.4') + s.add_development_dependency('cucumber', '~> 1.2.1') + s.add_development_dependency('aruba') + s.add_development_dependency('nokogiri') + s.add_development_dependency('capybara') + s.add_development_dependency('bundler') + s.add_development_dependency('cocaine', '~> 0.2') + s.add_development_dependency('fog', '~> 1.4.0') + s.add_development_dependency('pry') + s.add_development_dependency('launchy') + s.add_development_dependency('rake') + s.add_development_dependency('fakeweb') + s.add_development_dependency('railties') + s.add_development_dependency('actionmailer') +end diff --git a/vendor/gems/paperclip/shoulda_macros/paperclip.rb b/vendor/gems/paperclip/shoulda_macros/paperclip.rb new file mode 100644 index 0000000..899f856 --- /dev/null +++ b/vendor/gems/paperclip/shoulda_macros/paperclip.rb @@ -0,0 +1,124 @@ +require 'paperclip/matchers' + +module Paperclip + # =Paperclip Shoulda Macros + # + # These macros are intended for use with shoulda, and will be included into + # your tests automatically. All of the macros use the standard shoulda + # assumption that the name of the test is based on the name of the model + # you're testing (that is, UserTest is the test for the User model), and + # will load that class for testing purposes. + module Shoulda + include Matchers + # This will test whether you have defined your attachment correctly by + # checking for all the required fields exist after the definition of the + # attachment. + def should_have_attached_file name + klass = self.name.gsub(/Test$/, '').constantize + matcher = have_attached_file name + should matcher.description do + assert_accepts(matcher, klass) + end + end + + # Tests for validations on the presence of the attachment. + def should_validate_attachment_presence name + klass = self.name.gsub(/Test$/, '').constantize + matcher = validate_attachment_presence name + should matcher.description do + assert_accepts(matcher, klass) + end + end + + # Tests that you have content_type validations specified. There are two + # options, :valid and :invalid. Both accept an array of strings. The + # strings should be a list of content types which will pass and fail + # validation, respectively. + def should_validate_attachment_content_type name, options = {} + klass = self.name.gsub(/Test$/, '').constantize + valid = [options[:valid]].flatten + invalid = [options[:invalid]].flatten + matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid) + should matcher.description do + assert_accepts(matcher, klass) + end + end + + # Tests to ensure that you have file size validations turned on. You + # can pass the same options to this that you can to + # validate_attachment_file_size - :less_than, :greater_than, and :in. + # :less_than checks that a file is less than a certain size, :greater_than + # checks that a file is more than a certain size, and :in takes a Range or + # Array which specifies the lower and upper limits of the file size. + def should_validate_attachment_size name, options = {} + klass = self.name.gsub(/Test$/, '').constantize + min = options[:greater_than] || (options[:in] && options[:in].first) || 0 + max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0) + range = (min..max) + matcher = validate_attachment_size(name).in(range) + should matcher.description do + assert_accepts(matcher, klass) + end + end + + # Stubs the HTTP PUT for an attachment using S3 storage. + # + # @example + # stub_paperclip_s3('user', 'avatar', 'png') + def stub_paperclip_s3(model, attachment, extension) + definition = model.gsub(" ", "_").classify.constantize. + attachment_definitions[attachment.to_sym] + + path = "http://s3.amazonaws.com/:id/#{definition[:path]}" + path.gsub!(/:([^\/\.]+)/) do |match| + "([^\/\.]+)" + end + + begin + FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK") + rescue NameError + raise NameError, "the stub_paperclip_s3 shoulda macro requires the fakeweb gem." + end + end + + # Stub S3 and return a file for attachment. Best with Factory Girl. + # Uses a strict directory convention: + # + # features/support/paperclip + # + # This method is used by the Paperclip-provided Cucumber step: + # + # When I attach a "demo_tape" "mp3" file to a "band" on S3 + # + # @example + # Factory.define :band_with_demo_tape, :parent => :band do |band| + # band.demo_tape { band.paperclip_fixture("band", "demo_tape", "png") } + # end + def paperclip_fixture(model, attachment, extension) + stub_paperclip_s3(model, attachment, extension) + base_path = File.join(File.dirname(__FILE__), "..", "..", + "features", "support", "paperclip") + File.new(File.join(base_path, model, "#{attachment}.#{extension}")) + end + end +end + +if defined?(ActionController::Integration::Session) + class ActionController::Integration::Session #:nodoc: + include Paperclip::Shoulda + end +end + +if defined?(FactoryGirl::Factory) + class FactoryGirl::Factory + include Paperclip::Shoulda #:nodoc: + end +else + class Factory + include Paperclip::Shoulda #:nodoc: + end +end + +class Test::Unit::TestCase #:nodoc: + extend Paperclip::Shoulda +end diff --git a/vendor/gems/paperclip/test/attachment_options_test.rb b/vendor/gems/paperclip/test/attachment_options_test.rb new file mode 100644 index 0000000..df5b998 --- /dev/null +++ b/vendor/gems/paperclip/test/attachment_options_test.rb @@ -0,0 +1,27 @@ +require './test/helper' + +class AttachmentOptionsTest < Test::Unit::TestCase + should "be a Hash" do + assert_kind_of Hash, Paperclip::AttachmentOptions.new({}) + end + + should "respond to []" do + assert Paperclip::AttachmentOptions.new({}).respond_to?(:[]) + end + + should "deliver the specified options through []" do + intended_options = {:specific_key => "specific value"} + attachment_options = Paperclip::AttachmentOptions.new(intended_options) + assert_equal "specific value", attachment_options[:specific_key] + end + + should "respond to []=" do + assert Paperclip::AttachmentOptions.new({}).respond_to?(:[]=) + end + + should "remember options set with []=" do + attachment_options = Paperclip::AttachmentOptions.new({}) + attachment_options[:foo] = "bar" + assert_equal "bar", attachment_options[:foo] + end +end diff --git a/vendor/gems/paperclip/test/attachment_test.rb b/vendor/gems/paperclip/test/attachment_test.rb new file mode 100644 index 0000000..d2550e1 --- /dev/null +++ b/vendor/gems/paperclip/test/attachment_test.rb @@ -0,0 +1,1277 @@ +# encoding: utf-8 +require './test/helper' +require 'paperclip/attachment' + +class Dummy; end + +class AttachmentTest < Test::Unit::TestCase + + should "process :original style first" do + file = File.new(fixture_file("50x50.png"), 'rb') + rebuild_class :styles => { :small => '100x>', :original => '42x42#' } + dummy = Dummy.new + dummy.avatar = file + dummy.save + + # :small avatar should be 42px wide (processed original), not 50px (preprocessed original) + assert_equal `identify -format "%w" "#{dummy.avatar.path(:small)}"`.strip, "42" + + file.close + end + + should "not delete styles that don't get reprocessed" do + file = File.new(fixture_file("50x50.png"), 'rb') + rebuild_class :styles => { :small => '100x>', + :large => '500x>', + :original => '42x42#' } + dummy = Dummy.new + dummy.avatar = file + dummy.save + + assert_file_exists(dummy.avatar.path(:small)) + assert_file_exists(dummy.avatar.path(:large)) + assert_file_exists(dummy.avatar.path(:original)) + + dummy.avatar.reprocess!(:small) + + assert_file_exists(dummy.avatar.path(:small)) + assert_file_exists(dummy.avatar.path(:large)) + assert_file_exists(dummy.avatar.path(:original)) + end + + should "handle a boolean second argument to #url" do + mock_url_generator_builder = MockUrlGeneratorBuilder.new + attachment = Paperclip::Attachment.new(:name, :instance, :url_generator => mock_url_generator_builder) + + attachment.url(:style_name, true) + assert mock_url_generator_builder.has_generated_url_with_options?(:timestamp => true, :escape => true) + + attachment.url(:style_name, false) + assert mock_url_generator_builder.has_generated_url_with_options?(:timestamp => false, :escape => true) + end + + should "pass the style and options through to the URL generator on #url" do + mock_url_generator_builder = MockUrlGeneratorBuilder.new + attachment = Paperclip::Attachment.new(:name, :instance, :url_generator => mock_url_generator_builder) + + attachment.url(:style_name, :options => :values) + assert mock_url_generator_builder.has_generated_url_with_options?(:options => :values) + end + + should "pass default options through when #url is given one argument" do + mock_url_generator_builder = MockUrlGeneratorBuilder.new + attachment = Paperclip::Attachment.new(:name, + :instance, + :url_generator => mock_url_generator_builder, + :use_timestamp => true) + + attachment.url(:style_name) + assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true) + end + + should "pass default style and options through when #url is given no arguments" do + mock_url_generator_builder = MockUrlGeneratorBuilder.new + attachment = Paperclip::Attachment.new(:name, + :instance, + :default_style => 'default style', + :url_generator => mock_url_generator_builder, + :use_timestamp => true) + + attachment.url + assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true) + assert mock_url_generator_builder.has_generated_url_with_style_name?('default style') + end + + should "pass the option :timestamp => true if :use_timestamp is true and :timestamp is not passed" do + mock_url_generator_builder = MockUrlGeneratorBuilder.new + attachment = Paperclip::Attachment.new(:name, + :instance, + :url_generator => mock_url_generator_builder, + :use_timestamp => true) + + attachment.url(:style_name) + assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true) + end + + should "pass the option :timestamp => false if :use_timestamp is false and :timestamp is not passed" do + mock_url_generator_builder = MockUrlGeneratorBuilder.new + attachment = Paperclip::Attachment.new(:name, + :instance, + :url_generator => mock_url_generator_builder, + :use_timestamp => false) + + attachment.url(:style_name) + assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => false) + end + + should "not change the :timestamp if :timestamp is passed" do + mock_url_generator_builder = MockUrlGeneratorBuilder.new + attachment = Paperclip::Attachment.new(:name, + :instance, + :url_generator => mock_url_generator_builder, + :use_timestamp => false) + + attachment.url(:style_name, :timestamp => true) + assert mock_url_generator_builder.has_generated_url_with_options?(:escape => true, :timestamp => true) + end + + should "return the path based on the url by default" do + @attachment = attachment :url => "/:class/:id/:basename" + @model = @attachment.instance + @model.id = 1234 + @model.avatar_file_name = "fake.jpg" + assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path + end + + should "default to a path that scales" do + avatar_attachment = attachment + model = avatar_attachment.instance + model.id = 1234 + model.avatar_file_name = "fake.jpg" + expected_path = "#{Rails.root}/public/system/fake_models/avatars/000/001/234/original/fake.jpg" + assert_equal expected_path, avatar_attachment.path + end + + context "Attachment default_options" do + setup do + rebuild_model + @old_default_options = Paperclip::Attachment.default_options.dup + @new_default_options = @old_default_options.merge({ + :path => "argle/bargle", + :url => "fooferon", + :default_url => "not here.png" + }) + end + + teardown do + Paperclip::Attachment.default_options.merge! @old_default_options + end + + should "be overrideable" do + Paperclip::Attachment.default_options.merge!(@new_default_options) + @new_default_options.keys.each do |key| + assert_equal @new_default_options[key], + Paperclip::Attachment.default_options[key] + end + end + + context "without an Attachment" do + setup do + @dummy = Dummy.new + end + + should "return false when asked exists?" do + assert !@dummy.avatar.exists? + end + end + + context "on an Attachment" do + setup do + @dummy = Dummy.new + @attachment = @dummy.avatar + end + + Paperclip::Attachment.default_options.keys.each do |key| + should "be the default_options for #{key}" do + assert_equal @old_default_options[key], + @attachment.instance_variable_get("@options")[key], + key + end + end + + context "when redefined" do + setup do + Paperclip::Attachment.default_options.merge!(@new_default_options) + @dummy = Dummy.new + @attachment = @dummy.avatar + end + + Paperclip::Attachment.default_options.keys.each do |key| + should "be the new default_options for #{key}" do + assert_equal @new_default_options[key], + @attachment.instance_variable_get("@options")[key], + key + end + end + end + end + end + + context "An attachment with similarly named interpolations" do + setup do + rebuild_model :path => ":id.omg/:id-bbq/:idwhat/:id_partition.wtf" + @dummy = Dummy.new + @dummy.stubs(:id).returns(1024) + @file = File.new(fixture_file("5k.png"), 'rb') + @dummy.avatar = @file + end + + teardown { @file.close } + + should "make sure that they are interpolated correctly" do + assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path + end + end + + context "An attachment with :timestamp interpolations" do + setup do + @file = StringIO.new("...") + @zone = 'UTC' + Time.stubs(:zone).returns(@zone) + @zone_default = 'Eastern Time (US & Canada)' + Time.stubs(:zone_default).returns(@zone_default) + end + + context "using default time zone" do + setup do + rebuild_model :path => ":timestamp", :use_default_time_zone => true + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "return a time in the default zone" do + assert_equal @dummy.avatar_updated_at.in_time_zone(@zone_default).to_s, @dummy.avatar.path + end + end + + context "using per-thread time zone" do + setup do + rebuild_model :path => ":timestamp", :use_default_time_zone => false + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "return a time in the per-thread zone" do + assert_equal @dummy.avatar_updated_at.in_time_zone(@zone).to_s, @dummy.avatar.path + end + end + end + + context "An attachment with :hash interpolations" do + setup do + @file = StringIO.new("...") + end + + should "raise if no secret is provided" do + @attachment = attachment :path => ":hash" + @attachment.assign @file + + assert_raise ArgumentError do + @attachment.path + end + end + + context "when secret is set" do + setup do + @attachment = attachment :path => ":hash", :hash_secret => "w00t" + @attachment.stubs(:instance_read).with(:updated_at).returns(Time.at(1234567890)) + @attachment.stubs(:instance_read).with(:file_name).returns("bla.txt") + @attachment.instance.id = 1234 + @attachment.assign @file + end + + should "interpolate the hash data" do + @attachment.expects(:interpolate).with(@attachment.options[:hash_data],anything).returns("interpolated_stuff") + @attachment.hash_key + end + + should "result in the correct interpolation" do + assert_equal "fake_models/avatars/1234/original/1234567890", @attachment.send(:interpolate,@attachment.options[:hash_data]) + end + + should "result in a correct hash" do + assert_equal "d22b617d1bf10016aa7d046d16427ae203f39fce", @attachment.path + end + + should "generate a hash digest with the correct style" do + OpenSSL::HMAC.expects(:hexdigest).with(anything, anything, "fake_models/avatars/1234/medium/1234567890") + @attachment.path("medium") + end + end + end + + context "An attachment with a :rails_env interpolation" do + setup do + @rails_env = "blah" + @id = 1024 + rebuild_model :path => ":rails_env/:id.png" + @dummy = Dummy.new + @dummy.stubs(:id).returns(@id) + @file = StringIO.new(".") + @dummy.avatar = @file + Rails.stubs(:env).returns(@rails_env) + end + + should "return the proper path" do + assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path + end + end + + context "An attachment with a default style and an extension interpolation" do + setup do + @attachment = attachment :path => ":basename.:extension", + :styles => { :default => ["100x100", :png] }, + :default_style => :default + @file = StringIO.new("...") + @file.stubs(:original_filename).returns("file.jpg") + end + should "return the right extension for the path" do + @attachment.assign(@file) + assert_equal "file.png", @attachment.path + end + end + + context "An attachment with :convert_options" do + setup do + rebuild_model :styles => { + :thumb => "100x100", + :large => "400x400" + }, + :convert_options => { + :all => "-do_stuff", + :thumb => "-thumbnailize" + } + @dummy = Dummy.new + @dummy.avatar + end + + should "report the correct options when sent #extra_options_for(:thumb)" do + assert_equal "-thumbnailize -do_stuff", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect + end + + should "report the correct options when sent #extra_options_for(:large)" do + assert_equal "-do_stuff", @dummy.avatar.send(:extra_options_for, :large) + end + end + + context "An attachment with :source_file_options" do + setup do + rebuild_model :styles => { + :thumb => "100x100", + :large => "400x400" + }, + :source_file_options => { + :all => "-density 400", + :thumb => "-depth 8" + } + @dummy = Dummy.new + @dummy.avatar + end + + should "report the correct options when sent #extra_source_file_options_for(:thumb)" do + assert_equal "-depth 8 -density 400", @dummy.avatar.send(:extra_source_file_options_for, :thumb), @dummy.avatar.source_file_options.inspect + end + + should "report the correct options when sent #extra_source_file_options_for(:large)" do + assert_equal "-density 400", @dummy.avatar.send(:extra_source_file_options_for, :large) + end + end + + context "An attachment with :only_process" do + setup do + rebuild_model :styles => { + :thumb => "100x100", + :large => "400x400" + }, + :only_process => [:thumb] + @file = StringIO.new("...") + @attachment = Dummy.new.avatar + end + + should "only process the provided style" do + @attachment.expects(:post_process).with(:thumb) + @attachment.expects(:post_process).with(:large).never + @attachment.assign(@file) + end + end + + context "An attachment with :convert_options that is a proc" do + setup do + rebuild_model :styles => { + :thumb => "100x100", + :large => "400x400" + }, + :convert_options => { + :all => lambda{|i| i.all }, + :thumb => lambda{|i| i.thumb } + } + Dummy.class_eval do + def all; "-all"; end + def thumb; "-thumb"; end + end + @dummy = Dummy.new + @dummy.avatar + end + + should "report the correct options when sent #extra_options_for(:thumb)" do + assert_equal "-thumb -all", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect + end + + should "report the correct options when sent #extra_options_for(:large)" do + assert_equal "-all", @dummy.avatar.send(:extra_options_for, :large) + end + end + + context "An attachment with :path that is a proc" do + setup do + rebuild_model :path => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" } + + @file = File.new(fixture_file("5k.png"), 'rb') + @dummyA = Dummy.new(:other => 'a') + @dummyA.avatar = @file + @dummyB = Dummy.new(:other => 'b') + @dummyB.avatar = @file + end + + teardown { @file.close } + + should "return correct path" do + assert_equal "path/a.png", @dummyA.avatar.path + assert_equal "path/b.png", @dummyB.avatar.path + end + end + + context "An attachment with :styles that is a proc" do + setup do + rebuild_model :styles => lambda{ |attachment| {:thumb => "50x50#", :large => "400x400"} } + + @attachment = Dummy.new.avatar + end + + should "have the correct geometry" do + assert_equal "50x50#", @attachment.styles[:thumb][:geometry] + end + end + + context "An attachment with conditional :styles that is a proc" do + setup do + rebuild_model :styles => lambda{ |attachment| attachment.instance.other == 'a' ? {:thumb => "50x50#"} : {:large => "400x400"} } + + @dummy = Dummy.new(:other => 'a') + end + + should "have the correct styles for the assigned instance values" do + assert_equal "50x50#", @dummy.avatar.styles[:thumb][:geometry] + assert_nil @dummy.avatar.styles[:large] + + @dummy.other = 'b' + + assert_equal "400x400", @dummy.avatar.styles[:large][:geometry] + assert_nil @dummy.avatar.styles[:thumb] + end + end + + geometry_specs = [ + [ lambda{|z| "50x50#" }, :png ], + lambda{|z| "50x50#" }, + { :geometry => lambda{|z| "50x50#" } } + ] + geometry_specs.each do |geometry_spec| + context "An attachment geometry like #{geometry_spec}" do + setup do + rebuild_model :styles => { :normal => geometry_spec } + @attachment = Dummy.new.avatar + end + + context "when assigned" do + setup do + @file = StringIO.new(".") + @attachment.assign(@file) + end + + should "have the correct geometry" do + assert_equal "50x50#", @attachment.styles[:normal][:geometry] + end + end + end + end + + context "An attachment with both 'normal' and hash-style styles" do + setup do + rebuild_model :styles => { + :normal => ["50x50#", :png], + :hash => { :geometry => "50x50#", :format => :png } + } + @dummy = Dummy.new + @attachment = @dummy.avatar + end + + [:processors, :whiny, :convert_options, :geometry, :format].each do |field| + should "have the same #{field} field" do + assert_equal @attachment.styles[:normal][field], @attachment.styles[:hash][field] + end + end + end + + context "An attachment with :processors that is a proc" do + setup do + class Paperclip::Test < Paperclip::Processor; end + @file = StringIO.new("...") + Paperclip::Test.stubs(:make).returns(@file) + + rebuild_model :styles => { :normal => '' }, :processors => lambda { |a| [ :test ] } + @attachment = Dummy.new.avatar + end + + context "when assigned" do + setup do + @attachment.assign(StringIO.new(".")) + end + + should "have the correct processors" do + assert_equal [ :test ], @attachment.styles[:normal][:processors] + end + end + end + + context "An attachment with erroring processor" do + setup do + rebuild_model :processor => [:thumbnail], :styles => { :small => '' }, :whiny_thumbnails => true + @dummy = Dummy.new + Paperclip::Thumbnail.expects(:make).raises(Paperclip::Error, "cannot be processed.") + @file = StringIO.new("...") + @file.stubs(:to_tempfile).returns(@file) + @dummy.avatar = @file + end + + should "correctly forward processing error message to the instance" do + @dummy.valid? + assert_contains @dummy.errors.full_messages, "Avatar cannot be processed." + end + end + + context "An attachment with multiple processors" do + setup do + class Paperclip::Test < Paperclip::Processor; end + @style_params = { :once => {:one => 1, :two => 2} } + rebuild_model :processors => [:thumbnail, :test], :styles => @style_params + @dummy = Dummy.new + @file = StringIO.new("...") + @file.stubs(:to_tempfile).returns(@file) + Paperclip::Test.stubs(:make).returns(@file) + Paperclip::Thumbnail.stubs(:make).returns(@file) + end + + context "when assigned" do + setup { @dummy.avatar = @file } + + before_should "call #make on all specified processors" do + Paperclip::Thumbnail.expects(:make).with(any_parameters).returns(@file) + Paperclip::Test.expects(:make).with(any_parameters).returns(@file) + end + + before_should "call #make with the right parameters passed as second argument" do + expected_params = @style_params[:once].merge({ + :processors => [:thumbnail, :test], + :whiny => true, + :convert_options => "", + :source_file_options => "" + }) + Paperclip::Thumbnail.expects(:make).with(anything, expected_params, anything).returns(@file) + end + + before_should "call #make with attachment passed as third argument" do + Paperclip::Test.expects(:make).with(anything, anything, @dummy.avatar).returns(@file) + end + end + end + + should "include the filesystem module when loading the filesystem storage" do + rebuild_model :storage => :filesystem + @dummy = Dummy.new + assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem) + end + + should "include the filesystem module even if capitalization is wrong" do + rebuild_model :storage => :FileSystem + @dummy = Dummy.new + assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem) + + rebuild_model :storage => :Filesystem + @dummy = Dummy.new + assert @dummy.avatar.is_a?(Paperclip::Storage::Filesystem) + end + + should "convert underscored storage name to camelcase" do + rebuild_model :storage => :not_here + @dummy = Dummy.new + exception = assert_raises(Paperclip::Errors::StorageMethodNotFound) do + @dummy.avatar + end + assert exception.message.include?("NotHere") + end + + should "raise an error if you try to include a storage module that doesn't exist" do + rebuild_model :storage => :not_here + @dummy = Dummy.new + assert_raises(Paperclip::Errors::StorageMethodNotFound) do + @dummy.avatar + end + end + + context "An attachment with styles but no processors defined" do + setup do + rebuild_model :processors => [], :styles => {:something => '1'} + @dummy = Dummy.new + @file = StringIO.new("...") + end + should "raise when assigned to" do + assert_raises(RuntimeError){ @dummy.avatar = @file } + end + end + + context "An attachment without styles and with no processors defined" do + setup do + rebuild_model :processors => [], :styles => {} + @dummy = Dummy.new + @file = StringIO.new("...") + end + should "not raise when assigned to" do + @dummy.avatar = @file + end + end + + context "Assigning an attachment with post_process hooks" do + setup do + rebuild_class :styles => { :something => "100x100#" } + Dummy.class_eval do + before_avatar_post_process :do_before_avatar + after_avatar_post_process :do_after_avatar + before_post_process :do_before_all + after_post_process :do_after_all + def do_before_avatar; end + def do_after_avatar; end + def do_before_all; end + def do_after_all; end + end + @file = StringIO.new(".") + @file.stubs(:to_tempfile).returns(@file) + @dummy = Dummy.new + Paperclip::Thumbnail.stubs(:make).returns(@file) + @attachment = @dummy.avatar + end + + should "call the defined callbacks when assigned" do + @dummy.expects(:do_before_avatar).with() + @dummy.expects(:do_after_avatar).with() + @dummy.expects(:do_before_all).with() + @dummy.expects(:do_after_all).with() + Paperclip::Thumbnail.expects(:make).returns(@file) + @dummy.avatar = @file + end + + should "not cancel the processing if a before_post_process returns nil" do + @dummy.expects(:do_before_avatar).with().returns(nil) + @dummy.expects(:do_after_avatar).with() + @dummy.expects(:do_before_all).with().returns(nil) + @dummy.expects(:do_after_all).with() + Paperclip::Thumbnail.expects(:make).returns(@file) + @dummy.avatar = @file + end + + should "cancel the processing if a before_post_process returns false" do + @dummy.expects(:do_before_avatar).never + @dummy.expects(:do_after_avatar).never + @dummy.expects(:do_before_all).with().returns(false) + @dummy.expects(:do_after_all) + Paperclip::Thumbnail.expects(:make).never + @dummy.avatar = @file + end + + should "cancel the processing if a before_avatar_post_process returns false" do + @dummy.expects(:do_before_avatar).with().returns(false) + @dummy.expects(:do_after_avatar) + @dummy.expects(:do_before_all).with().returns(true) + @dummy.expects(:do_after_all) + Paperclip::Thumbnail.expects(:make).never + @dummy.avatar = @file + end + end + + context "Assigning an attachment" do + setup do + rebuild_model :styles => { :something => "100x100#" } + @file = StringIO.new(".") + @file.stubs(:original_filename).returns("5k.png\n\n") + @file.stubs(:content_type).returns("image/png\n\n") + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "strip whitespace from original_filename field" do + assert_equal "5k.png", @dummy.avatar.original_filename + end + + should "strip whitespace from content_type field" do + assert_equal "image/png", @dummy.avatar.instance.avatar_content_type + end + end + + context "Assigning an attachment" do + setup do + rebuild_model :styles => { :something => "100x100#" } + @file = StringIO.new(".") + @file.stubs(:original_filename).returns("5k.png\n\n") + @file.stubs(:content_type).returns(MIME::Type.new("image/png")) + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "make sure the content_type is a string" do + assert_equal "image/png", @dummy.avatar.instance.avatar_content_type + end + end + + context "Attachment with strange letters" do + setup do + rebuild_model + + @file = StringIO.new(".") + @file.stubs(:original_filename).returns("sheep_say_bæ.png\r\n") + @file.stubs(:content_type).returns("image/png\r\n") + + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "not remove strange letters" do + assert_equal "sheep_say_bæ.png", @dummy.avatar.original_filename + end + end + + context "Attachment with reserved filename" do + setup do + rebuild_model + @file = Tempfile.new(["filename","png"]) + end + + teardown do + @file.unlink + end + + context "with default configuration" do + "&$+,/:;=?@<>[]{}|\^~%# ".split(//).each do |character| + context "with character #{character}" do + + context "at beginning of filename" do + setup do + @file.stubs(:original_filename).returns("#{character}filename.png") + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "convert special character into underscore" do + assert_equal "_filename.png", @dummy.avatar.original_filename + end + end + + context "at end of filename" do + setup do + @file.stubs(:original_filename).returns("filename.png#{character}") + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "convert special character into underscore" do + assert_equal "filename.png_", @dummy.avatar.original_filename + end + end + + context "in the middle of filename" do + setup do + @file.stubs(:original_filename).returns("file#{character}name.png") + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "convert special character into underscore" do + assert_equal "file_name.png", @dummy.avatar.original_filename + end + end + + end + end + end + + context "with specified regexp replacement" do + setup do + @old_defaults = Paperclip::Attachment.default_options.dup + end + + teardown do + Paperclip::Attachment.default_options.merge! @old_defaults + end + + context 'as another regexp' do + setup do + Paperclip::Attachment.default_options.merge! :restricted_characters => /o/ + + @file.stubs(:original_filename).returns("goood.png") + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "match and convert that character" do + assert_equal "g___d.png", @dummy.avatar.original_filename + end + end + + context 'as nil' do + setup do + Paperclip::Attachment.default_options.merge! :restricted_characters => nil + + @file.stubs(:original_filename).returns("goood.png") + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "ignore and return the original file name" do + assert_equal "goood.png", @dummy.avatar.original_filename + end + end + end + end + + context "Attachment with uppercase extension and a default style" do + setup do + @old_defaults = Paperclip::Attachment.default_options.dup + Paperclip::Attachment.default_options.merge!({ + :path => ":rails_root/:attachment/:class/:style/:id/:basename.:extension" + }) + FileUtils.rm_rf("tmp") + rebuild_model + @instance = Dummy.new + @instance.stubs(:id).returns 123 + + @file = File.new(fixture_file("uppercase.PNG"), 'rb') + + styles = {:styles => { :large => ["400x400", :jpg], + :medium => ["100x100", :jpg], + :small => ["32x32#", :jpg]}, + :default_style => :small} + @attachment = Paperclip::Attachment.new(:avatar, + @instance, + styles) + now = Time.now + Time.stubs(:now).returns(now) + @attachment.assign(@file) + @attachment.save + end + + teardown do + @file.close + Paperclip::Attachment.default_options.merge!(@old_defaults) + end + + should "should have matching to_s and url methods" do + assert_match @attachment.to_s, @attachment.url + assert_match @attachment.to_s(:small), @attachment.url(:small) + end + end + + context "An attachment" do + setup do + @old_defaults = Paperclip::Attachment.default_options.dup + Paperclip::Attachment.default_options.merge!({ + :path => ":rails_root/:attachment/:class/:style/:id/:basename.:extension" + }) + FileUtils.rm_rf("tmp") + rebuild_model + @instance = Dummy.new + @instance.stubs(:id).returns 123 + @attachment = Paperclip::Attachment.new(:avatar, @instance) + @file = File.new(fixture_file("5k.png"), 'rb') + end + + teardown do + @file.close + Paperclip::Attachment.default_options.merge!(@old_defaults) + end + + should "raise if there are not the correct columns when you try to assign" do + @other_attachment = Paperclip::Attachment.new(:not_here, @instance) + assert_raises(Paperclip::Error) do + @other_attachment.assign(@file) + end + end + + should "return nil as path when no file assigned" do + assert_equal nil, @attachment.path + assert_equal nil, @attachment.path(:blah) + end + + context "with a file assigned but not saved yet" do + should "clear out any attached files" do + @attachment.assign(@file) + assert !@attachment.queued_for_write.blank? + @attachment.clear + assert @attachment.queued_for_write.blank? + end + end + + context "with a file assigned in the database" do + setup do + @attachment.stubs(:instance_read).with(:file_name).returns("5k.png") + @attachment.stubs(:instance_read).with(:content_type).returns("image/png") + @attachment.stubs(:instance_read).with(:file_size).returns(12345) + dtnow = DateTime.now + @now = Time.now + Time.stubs(:now).returns(@now) + @attachment.stubs(:instance_read).with(:updated_at).returns(dtnow) + end + + should "return the proper path when filename has a single .'s" do + assert_equal File.expand_path("tmp/avatars/dummies/original/#{@instance.id}/5k.png"), File.expand_path(@attachment.path) + end + + should "return the proper path when filename has multiple .'s" do + @attachment.stubs(:instance_read).with(:file_name).returns("5k.old.png") + assert_equal File.expand_path("tmp/avatars/dummies/original/#{@instance.id}/5k.old.png"), File.expand_path(@attachment.path) + end + + context "when expecting three styles" do + setup do + styles = {:styles => { :large => ["400x400", :png], + :medium => ["100x100", :gif], + :small => ["32x32#", :jpg]}} + @attachment = Paperclip::Attachment.new(:avatar, + @instance, + styles) + end + + context "and assigned a file" do + setup do + now = Time.now + Time.stubs(:now).returns(now) + @attachment.assign(@file) + end + + should "be dirty" do + assert @attachment.dirty? + end + + context "and saved" do + setup do + @attachment.save + end + + should "commit the files to disk" do + [:large, :medium, :small].each do |style| + assert_file_exists(@attachment.path(style)) + end + end + + should "save the files as the right formats and sizes" do + [[:large, 400, 61, "PNG"], + [:medium, 100, 15, "GIF"], + [:small, 32, 32, "JPEG"]].each do |style| + cmd = %Q[identify -format "%w %h %b %m" "#{@attachment.path(style.first)}"] + out = `#{cmd}` + width, height, size, format = out.split(" ") + assert_equal style[1].to_s, width.to_s + assert_equal style[2].to_s, height.to_s + assert_equal style[3].to_s, format.to_s + end + end + + context "and trying to delete" do + setup do + @existing_names = @attachment.styles.keys.collect do |style| + @attachment.path(style) + end + end + + should "delete the files after assigning nil" do + @attachment.expects(:instance_write).with(:file_name, nil) + @attachment.expects(:instance_write).with(:content_type, nil) + @attachment.expects(:instance_write).with(:file_size, nil) + @attachment.expects(:instance_write).with(:fingerprint, nil) + @attachment.expects(:instance_write).with(:updated_at, nil) + @attachment.assign nil + @attachment.save + @existing_names.each{|f| assert_file_not_exists(f) } + end + + should "delete the files when you call #clear and #save" do + @attachment.expects(:instance_write).with(:file_name, nil) + @attachment.expects(:instance_write).with(:content_type, nil) + @attachment.expects(:instance_write).with(:file_size, nil) + @attachment.expects(:instance_write).with(:fingerprint, nil) + @attachment.expects(:instance_write).with(:updated_at, nil) + @attachment.clear + @attachment.save + @existing_names.each{|f| assert_file_not_exists(f) } + end + + should "delete the files when you call #delete" do + @attachment.expects(:instance_write).with(:file_name, nil) + @attachment.expects(:instance_write).with(:content_type, nil) + @attachment.expects(:instance_write).with(:file_size, nil) + @attachment.expects(:instance_write).with(:fingerprint, nil) + @attachment.expects(:instance_write).with(:updated_at, nil) + @attachment.destroy + @existing_names.each{|f| assert_file_not_exists(f) } + end + + context "when keeping old files" do + setup do + @attachment.options[:keep_old_files] = true + end + + should "keep the files after assigning nil" do + @attachment.expects(:instance_write).with(:file_name, nil) + @attachment.expects(:instance_write).with(:content_type, nil) + @attachment.expects(:instance_write).with(:file_size, nil) + @attachment.expects(:instance_write).with(:fingerprint, nil) + @attachment.expects(:instance_write).with(:updated_at, nil) + @attachment.assign nil + @attachment.save + @existing_names.each{|f| assert_file_exists(f) } + end + + should "keep the files when you call #clear and #save" do + @attachment.expects(:instance_write).with(:file_name, nil) + @attachment.expects(:instance_write).with(:content_type, nil) + @attachment.expects(:instance_write).with(:file_size, nil) + @attachment.expects(:instance_write).with(:fingerprint, nil) + @attachment.expects(:instance_write).with(:updated_at, nil) + @attachment.clear + @attachment.save + @existing_names.each{|f| assert_file_exists(f) } + end + + should "keep the files when you call #delete" do + @attachment.expects(:instance_write).with(:file_name, nil) + @attachment.expects(:instance_write).with(:content_type, nil) + @attachment.expects(:instance_write).with(:file_size, nil) + @attachment.expects(:instance_write).with(:fingerprint, nil) + @attachment.expects(:instance_write).with(:updated_at, nil) + @attachment.destroy + @existing_names.each{|f| assert_file_exists(f) } + end + end + end + end + end + end + end + + context "when trying a nonexistant storage type" do + setup do + rebuild_model :storage => :not_here + end + + should "not be able to find the module" do + assert_raise(Paperclip::Errors::StorageMethodNotFound){ Dummy.new.avatar } + end + end + end + + context "An attachment with only a avatar_file_name column" do + setup do + ActiveRecord::Base.connection.create_table :dummies, :force => true do |table| + table.column :avatar_file_name, :string + end + rebuild_class + @dummy = Dummy.new + @file = File.new(fixture_file("5k.png"), 'rb') + end + + teardown { @file.close } + + should "not error when assigned an attachment" do + assert_nothing_raised { @dummy.avatar = @file } + end + + should "return the time when sent #avatar_updated_at" do + now = Time.now + Time.stubs(:now).returns(now) + @dummy.avatar = @file + assert_equal now.to_i, @dummy.avatar.updated_at.to_i + end + + should "return nil when reloaded and sent #avatar_updated_at" do + @dummy.save + @dummy.reload + assert_nil @dummy.avatar.updated_at + end + + should "return the right value when sent #avatar_file_size" do + @dummy.avatar = @file + assert_equal File.size(@file), @dummy.avatar.size + end + + context "and avatar_created_at column" do + setup do + ActiveRecord::Base.connection.add_column :dummies, :avatar_created_at, :timestamp + rebuild_class + @dummy = Dummy.new + end + + should "not error when assigned an attachment" do + assert_nothing_raised { @dummy.avatar = @file } + end + + should "return the creation time when sent #avatar_created_at" do + now = Time.now + Time.stubs(:now).returns(now) + @dummy.avatar = @file + assert_equal now.to_i, @dummy.avatar.created_at + end + + should "return the creation time when sent #avatar_created_at and the entry has been updated" do + creation = 2.hours.ago + now = Time.now + Time.stubs(:now).returns(creation) + @dummy.avatar = @file + Time.stubs(:now).returns(now) + @dummy.avatar = @file + assert_equal creation.to_i, @dummy.avatar.created_at + assert_not_equal now.to_i, @dummy.avatar.created_at + end + end + + context "and avatar_updated_at column" do + setup do + ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp + rebuild_class + @dummy = Dummy.new + end + + should "not error when assigned an attachment" do + assert_nothing_raised { @dummy.avatar = @file } + end + + should "return the right value when sent #avatar_updated_at" do + now = Time.now + Time.stubs(:now).returns(now) + @dummy.avatar = @file + assert_equal now.to_i, @dummy.avatar.updated_at + end + end + + should "not calculate fingerprint" do + @dummy.avatar = @file + assert_nil @dummy.avatar.fingerprint + end + + context "and avatar_content_type column" do + setup do + ActiveRecord::Base.connection.add_column :dummies, :avatar_content_type, :string + rebuild_class + @dummy = Dummy.new + end + + should "not error when assigned an attachment" do + assert_nothing_raised { @dummy.avatar = @file } + end + + should "return the right value when sent #avatar_content_type" do + @dummy.avatar = @file + assert_equal "image/png", @dummy.avatar.content_type + end + end + + context "and avatar_file_size column" do + setup do + ActiveRecord::Base.connection.add_column :dummies, :avatar_file_size, :integer + rebuild_class + @dummy = Dummy.new + end + + should "not error when assigned an attachment" do + assert_nothing_raised { @dummy.avatar = @file } + end + + should "return the right value when sent #avatar_file_size" do + @dummy.avatar = @file + assert_equal File.size(@file), @dummy.avatar.size + end + + should "return the right value when saved, reloaded, and sent #avatar_file_size" do + @dummy.avatar = @file + @dummy.save + @dummy = Dummy.find(@dummy.id) + assert_equal File.size(@file), @dummy.avatar.size + end + end + + context "and avatar_fingerprint column" do + setup do + ActiveRecord::Base.connection.add_column :dummies, :avatar_fingerprint, :string + rebuild_class + @dummy = Dummy.new + end + + should "not error when assigned an attachment" do + assert_nothing_raised { @dummy.avatar = @file } + end + + should "return the right value when sent #avatar_fingerprint" do + @dummy.avatar = @file + assert_equal 'aec488126c3b33c08a10c3fa303acf27', @dummy.avatar_fingerprint + end + + should "return the right value when saved, reloaded, and sent #avatar_fingerprint" do + @dummy.avatar = @file + @dummy.save + @dummy = Dummy.find(@dummy.id) + assert_equal 'aec488126c3b33c08a10c3fa303acf27', @dummy.avatar_fingerprint + end + end + end + + context "an attachment with delete_file option set to false" do + setup do + rebuild_model :preserve_files => true + @dummy = Dummy.new + @file = File.new(fixture_file("5k.png"), 'rb') + @dummy.avatar = @file + @dummy.save! + @attachment = @dummy.avatar + @path = @attachment.path + end + + teardown { @file.close } + + should "not delete the files from storage when attachment is destroyed" do + @attachment.destroy + assert_file_exists(@path) + end + + should "not delete the file when model is destroyed" do + @dummy.destroy + assert_file_exists(@path) + end + end + + context "An attached file" do + setup do + rebuild_model + @dummy = Dummy.new + @file = File.new(fixture_file("5k.png"), 'rb') + @dummy.avatar = @file + @dummy.save! + @attachment = @dummy.avatar + @path = @attachment.path + end + + teardown { @file.close } + + should "not be deleted when the model fails to destroy" do + @dummy.stubs(:destroy).raises(Exception) + + assert_raise Exception do + @dummy.destroy + end + + assert_file_exists(@path) + end + + should "be deleted when the model is destroyed" do + @dummy.destroy + assert_file_not_exists(@path) + end + end + +end diff --git a/vendor/gems/paperclip/test/content_type_detector_test.rb b/vendor/gems/paperclip/test/content_type_detector_test.rb new file mode 100644 index 0000000..f326662 --- /dev/null +++ b/vendor/gems/paperclip/test/content_type_detector_test.rb @@ -0,0 +1,40 @@ +require './test/helper' + +class ContentTypeDetectorTest < Test::Unit::TestCase + context 'given a name' do + should 'return a content type based on that name' do + @filename = "/path/to/something.jpg" + assert_equal "image/jpeg", Paperclip::ContentTypeDetector.new(@filename).detect + end + + should 'return a content type based on the content of the file' do + tempfile = Tempfile.new("something") + tempfile.write("This is a file.") + tempfile.rewind + + assert_equal "text/plain", Paperclip::ContentTypeDetector.new(tempfile.path).detect + end + + should 'return an empty content type if the file is empty' do + tempfile = Tempfile.new("something") + tempfile.rewind + + assert_equal "inode/x-empty", Paperclip::ContentTypeDetector.new(tempfile.path).detect + end + + should 'return a sensible default if no filename is supplied' do + assert_equal "application/octet-stream", Paperclip::ContentTypeDetector.new('').detect + end + + should 'return a sensible default if something goes wrong' do + @filename = "/path/to/something" + assert_equal "application/octet-stream", Paperclip::ContentTypeDetector.new(@filename).detect + end + + should 'return a sensible default when the file command is missing' do + Paperclip.stubs(:run).raises(Cocaine::CommandLineError.new) + @filename = "/path/to/something" + assert_equal "application/octet-stream", Paperclip::ContentTypeDetector.new(@filename).detect + end + end +end diff --git a/vendor/gems/paperclip/test/database.yml b/vendor/gems/paperclip/test/database.yml new file mode 100644 index 0000000..c12ad28 --- /dev/null +++ b/vendor/gems/paperclip/test/database.yml @@ -0,0 +1,4 @@ +test: + adapter: sqlite3 + database: ":memory:" + diff --git a/vendor/gems/paperclip/test/fixtures/12k.png b/vendor/gems/paperclip/test/fixtures/12k.png new file mode 100644 index 0000000000000000000000000000000000000000..f819d45195c3155fce332105f54d089f77b6f05c GIT binary patch literal 12093 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 t!6W285(nkRKP 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 BW5WOd literal 0 HcmV?d00001 diff --git a/vendor/gems/paperclip/test/fixtures/animated b/vendor/gems/paperclip/test/fixtures/animated new file mode 100644 index 0000000000000000000000000000000000000000..7eca29038a50532be35b4beb18c08840154571ba GIT binary patch literal 8238 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 M*!1h~`(M}p0RjuIr~m)} literal 0 HcmV?d00001 diff --git a/vendor/gems/paperclip/test/fixtures/animated.gif b/vendor/gems/paperclip/test/fixtures/animated.gif new file mode 100644 index 0000000000000000000000000000000000000000..7eca29038a50532be35b4beb18c08840154571ba GIT binary patch literal 8238 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 M*!1h~`(M}p0RjuIr~m)} literal 0 HcmV?d00001 diff --git a/vendor/gems/paperclip/test/fixtures/animated.unknown b/vendor/gems/paperclip/test/fixtures/animated.unknown new file mode 100644 index 0000000000000000000000000000000000000000..7eca29038a50532be35b4beb18c08840154571ba GIT binary patch literal 8238 zcmb7}c{o&k_kn8+mf=DXhDil9b;dcY)N4(Ns%R4 zLK;h=Buh#q#!{4&_T_%cPxte?@9*<`fB*D5f1T@G=lWddkJt6yj+KoiXQQVN!UypY zfmmEzL?94pX=#3be){_QhK7backYyzm#5Ka8X6iqcI>dTvpaF(L|j}PlgV6q^XSo| z=H}*jJbvl^cP^!$a%RE4o*}+IjL>}{ehg24&#?W6JbkqG9o%yWq4W(xV3v+iS|n_- z*x_Kt<6CHJu|ee0BaL6D|N2M-YLUT6^Vyg0ce!%^Tx+1l{g4CWVU=$q$F=cbhm{5Z z#l*koQRoHNJA=={uKQA6hJ?*G$v%1BzIj(v$El#lm^Dmg;X}j^dPM}7xNEncLOUkZ zkP;DlRGmb2z--YXQBnn~QLzz_7D(W+0X*+?Iun-_?r2Ft=kF|JQcGy17NmSMsX{fT zB;N9rs6jb1_V`T!h$G{gJMZ@1>$~4SFgWyJSRDWh9$FAF*m2CAryGH>aY_9Y(R2ei z^Aj)N$(R^T4`koVr6vvN4sl^ zZ=YhACCS@dE$PW%sV?H!d{_+ZUSrSL$o&9=If_b#JJ-W%f?R(-Yhz8P`8Jw^Epb^* z$jgM1HIPPvC=nHvtEW&F`28aiQEAo|zU=9dibs{241Ox5ZLFlg_`pG<$bNzaci=GPQLW3+x66AcS@A>!8py}rPm-?g|bQILE z&84YxMQz>OqiXNF`S#8A^G_QOSMlth!qMZ|w{a59jxUEKGQ+a);i-{B=W%$Z|!6bm4XArQwujXW^8Uy%*olkp8+-3g_XxB+m(y0z=dGqJj4|C)9Z!VJ22W6^< zq$xyE9+SunOgSn5X*CEWW$<<{(5=C-qf#jSw3vlhTAP4iydDw5)bG9n;GJ@Eq)6OG z&-ip*kwk#W6?0S7=-f&3hF;TUX)1wd;5c?sKNtL*Iy5N6?c|(Gf!H?O)9aO#d;y!H zkBp4hJ!+1b>%Ogik>iuSuN14aMCD&}B7X}-3idA)!5DJ^9h=fWDygX;7dgwBxwtFG zJ+3AU&YO15F1y}hc=5P(Y^|!tx&rk!;2lR}Onr%$7>tKHVgJcvWT~_?49o(eV`51f zq(nzT*jnnT!sK+OO?Ea~%bJp-Nh>^sEm6f^%*I<(7uGV-^)Zd+O=(wKlrwMU8=*Ri zyTGiNKP-+^lkaqq@DP3^uy+S)tZVxDi<#M%ujXFQPXj>3)Eh&9iunqC_{jPEbrEuX zCp`eS89Of06ek~vVZlLt2J>HM{22(e)H03C0S{&44Pf0GHX|?&0Z2E)Lx(6sZ}dw8H1{JI`G&AEvtbzYx-(OC0V)5Z!Y`5lh()%^v}5s^O6euw5y|M zL;09Yqy<)O=hp?#p6dSZj)gYwcnH^q?LXFLYrnV$ui5%A^85UYZdIr=?`cucaP;11 zxx21JY?!D!?S4^gd+}1#MSh0*?!K1mA7=WCHy3QVVQ@2baD|37AFi}0-AHE!3PmlU z*?46-XO%bd+q+Sd0%@UfyC6wVSsZ^V&pjIZ8ObWZ<1@eHbH-O7B%f>IiN}Au(-9q!KRP=R&xm6w>jm**EHAsd=-Ga>Jo=(b!HS^wF>1OXlRzt94|0qxsG z@rq)YD?@K9Neg1hN?BjXvmV81T6l4fvRY-}6*u7f7c}*xofXY_2%RP$b;)!tWM?k_2g>3#TUlvLE zz0$?#Z|xmws)LNGcH4xY`j1p3B(0^h*y4{6xX*VbzwF#&=WQzo3;r4A?$KK2W)P-M2om;7igzF8|Q8g-zJE%f*>Is~^heDa7hkq^F-Ajz(eC$|S)8 zqW-K1OC$#72cjt+N)cSFhzkw+e#l%YyCtC`jvAO_zf!h|Ixa=k=vUSnG4w5rJ@Cmx zoc!6PIBn&|p9~m@uL!eWp;u}#0(X+OaUm<#UU51~re7s!gXKQ2;1CU$pr|z)(q1|mO<2=Q)G2$&@Y*E-O~eq7tpUkGp^GL!5E5e7kP;G;&ZZ(rkz|YzcOaxp z6PTW$>AyQ*DPqd3*vx* zUQvAYDbu55HrkHgG1z7-GqH$lWCfE+=+#ojF}L~Cc1na-&mzA_6+IEY0$;b?wnDTdboe1&h9>z3`6nm zYbd&F-=h=xqu)M^bzj+VNR8O6oro&FCAW!M800Qf@8(@fd{X=RNr(42w(w8nH2=nM zrAT4LwS~zl&wz%!XXb95fu92P%6EuEYkaF(tBW0P{P|^J>Tbc-o2@^7*|LAhq#c*Ppk?F^r_fUIr^dcv!#_or#tpyEo-NR& znlD(TtZh0wRhXc5dA3L^=5KJJ-vXEG+Tq~`oidE}KE`Gg=%Ww6^x#&lR~;Vc40ns` zcu@E?*CBd7c)_0w?R7Gvx;VQN-ULt3C}fx#7_^#%iznkkH9-P(zt%}ODrPkyAwH5z z%*cz*Qla7!i*Y>9JeVS49?L=T&R@b7PVW6H zfBsuPjR7kJv%R8OL_P%r8rndCnKl-_D zI^~B?4OcLboT4nV5MNuityqek3p2LnKcGn|p6|ZAEWl|=KVwjoqZQancYEEiJF;zf z)O&olo9E{Wr`p}`#_TuiYd&iA`j!uiv#;=_3DZs&)alh@H&x!hS4ehRCy@s6CPq62 z{=?-__SWPBkFP#!&G=G85xEGSE(0^;ymu}S_8D#n_;dbpbCCKyQG8n9D}%-bDnJi3 z{twe{`ZRYO?~}z~S%Dc3o&PwBXhlI6sM&5qyT0+-Lv0Bm;}4DJ=2npK-H&!}cKgw^ z-kdTX`NWz(j?~u`Iv)+6ss7_}#FUsRMrJ8-@(>6k7!fmazUo26r`*DR9c;OeLJYR{ZTj2wSKi2Ah4?8 zbQ61<=UESVT;EgoJ#8j`^bzu zfdQK}%{pf@MRWE$-AUb>uF$x|_sbtg9K;b1fQLg=0fHlr>Y)XIf^}$edO8@h+6_v` z;%+Bn(lJiis_3(&1RgP?{CKf4Sn7zrRFI#sr4m7eN{e{-a&ql;6(X8|k2WEp>v}q4 zD3s*>;s3F66Tl3(qccfqlmIBqJoWkd>K63EV-hs~?X4aFp<1A_Bi{XFUMu+0Yjo<< zp^ObE3`vnvMqyMrJ!8oCo?*vDorGi`bUYM07(*z_#qXYMh7<>(`r%u6j~N0)kqid^ zpZuv@6TR(ak|ve{ee@Q?TM8&e{&|f>ycob^O>WqN1bTc`wXf81v*0zh0U>Gi4Jays zu52Sz(D{-82&F#VmzwMpio{-b~5Jh&(V4v zJTkp*yV?L^|A_e1^632e>w%L`3hgUwt~AUc%Gcr!mEPUhuy8wgf;_(S+Z(5vE$XWi zY$g67H2ptzkN4eZ{_4d3zlt5JH&{Pn8=L}2&*$H+hI%VjdkiIToNpao$^1_qrJe=(#|Fsdy zSD-RYCy%kaerYJWj_g8vuw2oKusKV#bhB{-bB5B8D zbe!^5`=$P+p~?T4Kl-=86^jDt(i*gwSh%}F6WiTcWD|=k;6x~Xu|3`=K^z2NDie=Fki7h#A;Jcu!Gb@3@>q){$ZISf4cMGf?JGzUyAo-yMFX1&G3Lxv2>{X;)y+*RmS4yQbxjwP6M9ChA0ahmuoLiIBKOf2^E3nx!rA!_09KQow*Gv} zjjH}c^KWo?v1*`C;uRcM>{fLBp=bEwg-b7T_5U$-%Io=RCyB04$aT#rVooPgm#?$(5a zfaH|4Q>KIr2$z+ONKH#JM(15D5Luy6v8Dh&0jda4YebV3{b>jBu>oMvE^!pdRFQ3iHj(V^wck5v1 zS_i^I`{>XSf~@|WQqcbba%oiZS&syXi*%3vz!*4N8l@2!D2@VmYoz(Oy_2*h<{NKD zEHlV7zfZM7GA&#`b|TL475decq!R^~ETi{0Ud}52qja~0hC-9>{v<3vU~Fd%0jHPm ze(SpiA@j{>rT5(AjR^hA_Ta|lEb*nHik-Dq^U4o%oohU4TlYONgtON6HknW9_xW?) zdiJgLu~gl!jk?pOvH3rNY_30`9OKlk@tDyTH4Ys;8O6Wu{C2#XfHhIFY8#HeNo82^ z&!fN@{8~jFhSB#!^XraotkOzBzW*@&cr14W=6)G>^SNlIc(Q(_>fJ+s0l6su(z-t* z*zC2b_^=o&fzR%uO=CI`SaX*9+DHx2BbckpD2%~yun@4^xHKAAX^W+^l#+75L{()8 znZ?+}iXuFs%mAU9+r#2wm%pe53~A7sHa8PRzE`|hUbH-2U4u~~1ZqS|@sgTCI9;or zDbCp19^$GX&l6t|3R=`&%Od^b z``F*5l@-86lZpn_5@hhn)4_NhP62qi-Z#vE^fWMOTcdWTpR`#ar$dg!bM@uZr_Dgm zg1mDT1geKADFQ-4+fS%aqf*Vl_&7>3(}_yOn^EE-lCl6Nd;ymbfr`%ZvO0LVvZ}h~ zYVEbU`i92qP0bDfh(69Cf|<=g1P-Ex=@AL{Z(R@X=^4;P-#^++cmUzrbo=g08t`K? z(>fG!%k3Fdi%}r~?0xs;>$mTJ{_DrjMFfbKx2_hoMc-g>tK<722!oSn`VWZ6m!zc> zJ$n6=i!Eu2CTS9ufZ20e(P0q_cc&wk>*qS1H^+IXS)G#$D(YQxb@9G)~*cpa4|c`|o1-|T19BBteH z{)zd=pFt4u)2iUGlP8nQh(LcAaRrFd<(80?tt6cg)Y6ZDKmNEak^N@mXOEXs;*KY- z`HJy?Dv4YX%(4|trfTFfxJbnI&v=N+r0~*KGXtkmsaN~C9zW!dO?z$`3!@{q9dxD( ztQ6UkJd%va3+a~jYXVJL@%NO3`+W}Ca=$p_YbYlzaTsl0NyQ#{DljZ?t)#1lS;`7( zo^--o`g))6JTE%^aA6(g^x$S9VT&h^M2(C*aZ(vgNJE<~Rrh4(E&*r;pc0N}sQ?r- zC5cNsPYo>a1}w|}i^CR!Djlw7G&mPtN8?vP$au2ULWVg5DYuNs z_KFNG^0diNm0v8~VFRclGPNu`b~rP5RsdlKi}I4sOUlt+)goii?qgSM9xZF}M0;P} zIeqCe15bEw$9Wb^e%6$EVX)ZdnEvzdBHn{@bEdAU`%Kk`F8DMEV&@zyhiK>eyzGOv zm2?#xi61+@Ql@NkPIO(i&}S$kDVDBaE^k?-d@dTN6EN0$y>{ll$ShgW-ryOWixYmz zb@k4Oc~nJkPD2&G03K>H#AB%ovM~@&9=c5`79SkJ1wLP<6;sh7(@#AAG3iccdb-g*Az zST+oJT&?D2`zcCB@s@$dHY5F%g{sH`s&G#>Dsm535cDd?<;cG6U~bL;oiTm#ul$Ml z$L{gJ3$C>^?V*MnV9}6Qys*+CPkMQte;c@Z)R@=f|g=cck=AJvBmtP<%EGjnnYsJhi;b5ey zs>tP{T5UpgBdVA}MG=y<$&J;tBH%{1en(>w33VSu6|*nimFgN8pz1f=Z7L$(r`qcQ z?Txg$JYY!YHmwTvs^a6P{{mMRiEM9)Vxk#BaW$n~2Bh0&O85mZa-hA!nRD866SZoK zSbKdsq?7T8yI4w0O!r+5uyH~&WMOgVS*Ak~BuSsC%U6neCBNnxsuzGFFO}_c5Lliz zu!2$z_V3-6wc7Ff4ytsiAk}{6(*xIQ8pDMq_b#(x8MQuX>L#ZcgZh#O*EEX{4mW+N z9FyL!V(3`NpB#JR-N4@d)VSYu=nZsve;E0= zr12sWEi>N8nfdtq!|YJ`?%ij3VRx#eBDe63FfjlQ4>j%Jwk|@ZT%i~yGpkpvB6Y{Z zkteb8v)~DonndjB&6SMDMj(`wqHw)k@d(0NRzb88W%)$R8S}siWczhC7b1j(0w8E> z%{}fYKeY<^0$B}%+@e_k3}a2oL7X%;SBOEZQ8C3sE2e|fRv>&bxO=|R!R`7sUfyVr zmvHuJUrS7cU+Gf*)cyxZ!v7bLQEmNP17F+Nc8^F`7vR#h@K2iZQk{akUUn;m2q#W51)-+s1Ohr)H7Zrg6g*AIWFAdLg@Yg^ zFf$;OR+?wEG+ypUl~?@j=q)!|)qZW87jBw>w4R=;T`IL~%AG;*o-GwTtXnnMgX?l3 zP^lSuxX~VR7ioez@mQNWLmRv4GC!q1F!SIQe(Jp5&LD56d3P}qqA}?U6=yr*DJr)A zQHb7QW4sjH*$f!L17Ib`MK*_jwFJsshSKH3sTNV$nhJEEm>%<$1^aC|lN*&;h+>PA zr5=2SDN?{dGCjxYdTc7fSXyoY;1;KC)}sYv&5vXTcUv68xUh ze&*8Lf)2krCllK7WSRQcfJcq-w{ktc>zX>!NY0ZnxNDk|oraow^)9=-OGIy$OY`Fn zGM{IzF;24lpF;HOrzUTj*8Vz9tb0Liwx3@9?7TTRJS+9j8V69hMEiF{fFCO6u zcX69OXy2u3{NX_?3;|FwJ=G_Ez~Q@8nm3!AFy%t;+NhMTC+>=m#p#bU$nSW??MEE% zo)2Q2Ke#l?OQI>3Sk$2g-wAOne&I}~((w^5aV*B8%nFHPnj-{ex`@$Ojn>=INY$HU<(jnik!RA(A8uHhT|qfp)~qN-w#gKW M*!1h~`(M}p0RjuIr~m)} literal 0 HcmV?d00001 diff --git a/vendor/gems/paperclip/test/fixtures/bad.png b/vendor/gems/paperclip/test/fixtures/bad.png new file mode 100644 index 0000000..7ba4f07 --- /dev/null +++ b/vendor/gems/paperclip/test/fixtures/bad.png @@ -0,0 +1 @@ +This is not an image. diff --git a/vendor/gems/paperclip/test/fixtures/fog.yml b/vendor/gems/paperclip/test/fixtures/fog.yml new file mode 100644 index 0000000..098ea3a --- /dev/null +++ b/vendor/gems/paperclip/test/fixtures/fog.yml @@ -0,0 +1,8 @@ +development: + provider: AWS + aws_access_key_id: AWS_ID + aws_secret_access_key: AWS_SECRET +test: + provider: AWS + aws_access_key_id: AWS_ID + aws_secret_access_key: AWS_SECRET diff --git a/vendor/gems/paperclip/test/fixtures/s3.yml b/vendor/gems/paperclip/test/fixtures/s3.yml new file mode 100644 index 0000000..13c8b0c --- /dev/null +++ b/vendor/gems/paperclip/test/fixtures/s3.yml @@ -0,0 +1,8 @@ +development: + key: 54321 +production: + key: 12345 +test: + bucket: <%= ENV['S3_BUCKET'] %> + access_key_id: <%= ENV['S3_KEY'] %> + secret_access_key: <%= ENV['S3_SECRET'] %> diff --git a/vendor/gems/paperclip/test/fixtures/spaced file.png b/vendor/gems/paperclip/test/fixtures/spaced file.png new file mode 100644 index 0000000000000000000000000000000000000000..75d9f0459e83ce495fdc71baef8c9279ae92d9b3 GIT binary patch literal 4456 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 BW5WOd literal 0 HcmV?d00001 diff --git a/vendor/gems/paperclip/test/fixtures/text.txt b/vendor/gems/paperclip/test/fixtures/text.txt new file mode 100644 index 0000000..3286a3f --- /dev/null +++ b/vendor/gems/paperclip/test/fixtures/text.txt @@ -0,0 +1 @@ +paperclip! diff --git a/vendor/gems/paperclip/test/fixtures/twopage.pdf b/vendor/gems/paperclip/test/fixtures/twopage.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0c34a5193f4404443922279f43c7f4f2309c0247 GIT binary patch literal 8775 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 H8SwuA2Tvy* literal 0 HcmV?d00001 diff --git a/vendor/gems/paperclip/test/fixtures/uppercase.PNG b/vendor/gems/paperclip/test/fixtures/uppercase.PNG new file mode 100644 index 0000000000000000000000000000000000000000..75d9f0459e83ce495fdc71baef8c9279ae92d9b3 GIT binary patch literal 4456 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 BW5WOd literal 0 HcmV?d00001 diff --git a/vendor/gems/paperclip/test/generator_test.rb b/vendor/gems/paperclip/test/generator_test.rb new file mode 100644 index 0000000..8b03084 --- /dev/null +++ b/vendor/gems/paperclip/test/generator_test.rb @@ -0,0 +1,80 @@ +require './test/helper' +require 'rails/generators' +require 'generators/paperclip/paperclip_generator' + +class GeneratorTest < Rails::Generators::TestCase + tests PaperclipGenerator + destination File.expand_path("../tmp", File.dirname(__FILE__)) + setup :prepare_destination + + context 'running migration' do + context 'with single attachment name' do + setup do + run_generator %w(user avatar) + end + + should 'create a correct migration file' do + assert_migration 'db/migrate/add_attachment_avatar_to_users.rb' do |migration| + assert_match /class AddAttachmentAvatarToUsers/, migration + + assert_class_method :up, migration do |up| + expected = <<-migration + change_table :users do |t| + t.has_attached_file :avatar + end + migration + + assert_equal expected.squish, up.squish + end + + assert_class_method :down, migration do |down| + expected = <<-migration + drop_attached_file :users, :avatar + migration + + assert_equal expected.squish, down.squish + end + end + end + end + + context 'with multiple attachment names' do + setup do + run_generator %w(user avatar photo) + end + + should 'create a correct migration file' do + assert_migration 'db/migrate/add_attachment_avatar_photo_to_users.rb' do |migration| + assert_match /class AddAttachmentAvatarPhotoToUsers/, migration + + assert_class_method :up, migration do |up| + expected = <<-migration + change_table :users do |t| + t.has_attached_file :avatar + t.has_attached_file :photo + end + migration + + assert_equal expected.squish, up.squish + end + + assert_class_method :down, migration do |down| + expected = <<-migration + drop_attached_file :users, :avatar + drop_attached_file :users, :photo + migration + + assert_equal expected.squish, down.squish + end + end + end + end + + context 'without required arguments' do + should 'not create the migration' do + silence_stream(STDERR) { run_generator %w() } + assert_no_migration 'db/migrate/add_attachment_avatar_to_users.rb' + end + end + end +end diff --git a/vendor/gems/paperclip/test/geometry_test.rb b/vendor/gems/paperclip/test/geometry_test.rb new file mode 100644 index 0000000..e288a8f --- /dev/null +++ b/vendor/gems/paperclip/test/geometry_test.rb @@ -0,0 +1,225 @@ +require './test/helper' + +class GeometryTest < Test::Unit::TestCase + context "Paperclip::Geometry" do + should "correctly report its given dimensions" do + assert @geo = Paperclip::Geometry.new(1024, 768) + assert_equal 1024, @geo.width + assert_equal 768, @geo.height + end + + should "set height to 0 if height dimension is missing" do + assert @geo = Paperclip::Geometry.new(1024) + assert_equal 1024, @geo.width + assert_equal 0, @geo.height + end + + should "set width to 0 if width dimension is missing" do + assert @geo = Paperclip::Geometry.new(nil, 768) + assert_equal 0, @geo.width + assert_equal 768, @geo.height + end + + should "be generated from a WxH-formatted string" do + assert @geo = Paperclip::Geometry.parse("800x600") + assert_equal 800, @geo.width + assert_equal 600, @geo.height + end + + should "be generated from a xH-formatted string" do + assert @geo = Paperclip::Geometry.parse("x600") + assert_equal 0, @geo.width + assert_equal 600, @geo.height + end + + should "be generated from a Wx-formatted string" do + assert @geo = Paperclip::Geometry.parse("800x") + assert_equal 800, @geo.width + assert_equal 0, @geo.height + end + + should "be generated from a W-formatted string" do + assert @geo = Paperclip::Geometry.parse("800") + assert_equal 800, @geo.width + assert_equal 0, @geo.height + end + + should "ensure the modifier is nil if not present" do + assert @geo = Paperclip::Geometry.parse("123x456") + assert_nil @geo.modifier + end + + should "treat x and X the same in geometries" do + @lower = Paperclip::Geometry.parse("123x456") + @upper = Paperclip::Geometry.parse("123X456") + assert_equal 123, @lower.width + assert_equal 123, @upper.width + assert_equal 456, @lower.height + assert_equal 456, @upper.height + end + + ['>', '<', '#', '@', '%', '^', '!', nil].each do |mod| + should "ensure the modifier #{mod.inspect} is preserved" do + assert @geo = Paperclip::Geometry.parse("123x456#{mod}") + assert_equal mod, @geo.modifier + assert_equal "123x456#{mod}", @geo.to_s + end + end + + ['>', '<', '#', '@', '%', '^', '!', nil].each do |mod| + should "ensure the modifier #{mod.inspect} is preserved with no height" do + assert @geo = Paperclip::Geometry.parse("123x#{mod}") + assert_equal mod, @geo.modifier + assert_equal "123#{mod}", @geo.to_s + end + end + + should "make sure the modifier gets passed during transformation_to" do + assert @src = Paperclip::Geometry.parse("123x456") + assert @dst = Paperclip::Geometry.parse("123x456>") + assert_equal ["123x456>", nil], @src.transformation_to(@dst) + end + + should "generate correct ImageMagick formatting string for W-formatted string" do + assert @geo = Paperclip::Geometry.parse("800") + assert_equal "800", @geo.to_s + end + + should "generate correct ImageMagick formatting string for Wx-formatted string" do + assert @geo = Paperclip::Geometry.parse("800x") + assert_equal "800", @geo.to_s + end + + should "generate correct ImageMagick formatting string for xH-formatted string" do + assert @geo = Paperclip::Geometry.parse("x600") + assert_equal "x600", @geo.to_s + end + + should "generate correct ImageMagick formatting string for WxH-formatted string" do + assert @geo = Paperclip::Geometry.parse("800x600") + assert_equal "800x600", @geo.to_s + end + + should "be generated from a file" do + file = fixture_file("5k.png") + file = File.new(file, 'rb') + assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) } + assert @geo.height > 0 + assert @geo.width > 0 + end + + should "be generated from a file path" do + file = fixture_file("5k.png") + assert_nothing_raised{ @geo = Paperclip::Geometry.from_file(file) } + assert @geo.height > 0 + assert @geo.width > 0 + end + + should "not generate from a bad file" do + file = "/home/This File Does Not Exist.omg" + assert_raise(Paperclip::Errors::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } + end + + should "not generate from a blank filename" do + file = "" + assert_raise(Paperclip::Errors::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } + end + + should "not generate from a nil file" do + file = nil + assert_raise(Paperclip::Errors::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } + end + + should "not generate from a file with no path" do + file = mock("file", :path => "") + file.stubs(:respond_to?).with(:path).returns(true) + assert_raise(Paperclip::Errors::NotIdentifiedByImageMagickError){ @geo = Paperclip::Geometry.from_file(file) } + end + + should "let us know when a command isn't found versus a processing error" do + old_path = ENV['PATH'] + begin + ENV['PATH'] = '' + assert_raises(Paperclip::Errors::CommandNotFoundError) do + file = fixture_file("5k.png") + @geo = Paperclip::Geometry.from_file(file) + end + ensure + ENV['PATH'] = old_path + end + end + + [['vertical', 900, 1440, true, false, false, 1440, 900, 0.625], + ['horizontal', 1024, 768, false, true, false, 1024, 768, 1.3333], + ['square', 100, 100, false, false, true, 100, 100, 1]].each do |args| + context "performing calculations on a #{args[0]} viewport" do + setup do + @geo = Paperclip::Geometry.new(args[1], args[2]) + end + + should "#{args[3] ? "" : "not"} be vertical" do + assert_equal args[3], @geo.vertical? + end + + should "#{args[4] ? "" : "not"} be horizontal" do + assert_equal args[4], @geo.horizontal? + end + + should "#{args[5] ? "" : "not"} be square" do + assert_equal args[5], @geo.square? + end + + should "report that #{args[6]} is the larger dimension" do + assert_equal args[6], @geo.larger + end + + should "report that #{args[7]} is the smaller dimension" do + assert_equal args[7], @geo.smaller + end + + should "have an aspect ratio of #{args[8]}" do + assert_in_delta args[8], @geo.aspect, 0.0001 + end + end + end + + [[ [1000, 100], [64, 64], "x64", "64x64+288+0" ], + [ [100, 1000], [50, 950], "x950", "50x950+22+0" ], + [ [100, 1000], [50, 25], "50x", "50x25+0+237" ]]. each do |args| + context "of #{args[0].inspect} and given a Geometry #{args[1].inspect} and sent transform_to" do + setup do + @geo = Paperclip::Geometry.new(*args[0]) + @dst = Paperclip::Geometry.new(*args[1]) + @scale, @crop = @geo.transformation_to @dst, true + end + + should "be able to return the correct scaling transformation geometry #{args[2]}" do + assert_equal args[2], @scale + end + + should "be able to return the correct crop transformation geometry #{args[3]}" do + assert_equal args[3], @crop + end + end + end + + [['256x256', '150x150!' => [150, 150], '150x150#' => [150, 150], '150x150>' => [150, 150], '150x150<' => [256, 256], '150x150' => [150, 150]], + ['256x256', '512x512!' => [512, 512], '512x512#' => [512, 512], '512x512>' => [256, 256], '512x512<' => [512, 512], '512x512' => [512, 512]], + ['600x400', '512x512!' => [512, 512], '512x512#' => [512, 512], '512x512>' => [512, 341], '512x512<' => [600, 400], '512x512' => [512, 341]]].each do |original_size, options| + options.each_pair do |size, dimensions| + context "#{original_size} resize_to #{size}" do + setup do + @source = Paperclip::Geometry.parse original_size + @new_geometry = @source.resize_to size + end + should "have #{dimensions.first} width" do + assert_equal dimensions.first, @new_geometry.width + end + should "have #{dimensions.last} height" do + assert_equal dimensions.last, @new_geometry.height + end + end + end + end + end +end diff --git a/vendor/gems/paperclip/test/helper.rb b/vendor/gems/paperclip/test/helper.rb new file mode 100644 index 0000000..fd29d25 --- /dev/null +++ b/vendor/gems/paperclip/test/helper.rb @@ -0,0 +1,187 @@ +require 'rubygems' +require 'tempfile' +require 'pathname' +require 'test/unit' + +require 'shoulda' +require 'mocha' +require 'bourne' + +require 'active_record' +require 'active_record/version' +require 'active_support' +require 'active_support/core_ext' +require 'mime/types' +require 'pathname' +require 'ostruct' + +puts "Testing against version #{ActiveRecord::VERSION::STRING}" + +`ruby -e 'exit 0'` # Prime $? with a value. + +begin + require 'ruby-debug' +rescue LoadError => e + puts "debugger disabled" +end + +ROOT = Pathname(File.expand_path(File.join(File.dirname(__FILE__), '..'))) + +class Test::Unit::TestCase + def setup + silence_warnings do + Object.const_set(:Rails, stub('Rails')) + Rails.stubs(:root).returns(Pathname.new(ROOT).join('tmp')) + Rails.stubs(:env).returns('test') + Rails.stubs(:const_defined?).with(:Railtie).returns(false) + end + end +end + +$LOAD_PATH << File.join(ROOT, 'lib') +$LOAD_PATH << File.join(ROOT, 'lib', 'paperclip') + +require File.join(ROOT, 'lib', 'paperclip.rb') + +require './shoulda_macros/paperclip' + +FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures") +config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) +ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(File.dirname(__FILE__) + "/debug.log") +ActiveRecord::Base.establish_connection(config['test']) +Paperclip.options[:logger] = ActiveRecord::Base.logger + +def require_everything_in_directory(directory_name) + Dir[File.join(File.dirname(__FILE__), directory_name, '*')].each do |f| + require f + end +end + +require_everything_in_directory('support') + +def reset_class class_name + ActiveRecord::Base.send(:include, Paperclip::Glue) + Object.send(:remove_const, class_name) rescue nil + klass = Object.const_set(class_name, Class.new(ActiveRecord::Base)) + + klass.class_eval do + include Paperclip::Glue + end + + klass.reset_column_information + klass.connection_pool.clear_table_cache!(klass.table_name) if klass.connection_pool.respond_to?(:clear_table_cache!) + klass.connection.schema_cache.clear_table_cache!(klass.table_name) if klass.connection.respond_to?(:schema_cache) + klass +end + +def reset_table table_name, &block + block ||= lambda { |table| true } + ActiveRecord::Base.connection.create_table :dummies, {:force => true}, &block +end + +def modify_table table_name, &block + ActiveRecord::Base.connection.change_table :dummies, &block +end + +def rebuild_model options = {} + ActiveRecord::Base.connection.create_table :dummies, :force => true do |table| + table.column :title, :string + table.column :other, :string + table.column :avatar_file_name, :string + table.column :avatar_content_type, :string + table.column :avatar_file_size, :integer + table.column :avatar_updated_at, :datetime + table.column :avatar_fingerprint, :string + end + rebuild_class options +end + +def rebuild_class options = {} + reset_class("Dummy").tap do |klass| + klass.has_attached_file :avatar, options + Paperclip.reset_duplicate_clash_check! + end +end + +class FakeModel + attr_accessor :avatar_file_name, + :avatar_file_size, + :avatar_updated_at, + :avatar_content_type, + :avatar_fingerprint, + :id + + def errors + @errors ||= [] + end + + def run_paperclip_callbacks name, *args + end +end + +def attachment(options={}) + Paperclip::Attachment.new(:avatar, FakeModel.new, options) +end + +def silence_warnings + old_verbose, $VERBOSE = $VERBOSE, nil + yield +ensure + $VERBOSE = old_verbose +end + +def should_accept_dummy_class + should "accept the class" do + assert_accepts @matcher, @dummy_class + end + + should "accept an instance of that class" do + assert_accepts @matcher, @dummy_class.new + end +end + +def should_reject_dummy_class + should "reject the class" do + assert_rejects @matcher, @dummy_class + end + + should "reject an instance of that class" do + assert_rejects @matcher, @dummy_class.new + end +end + +def with_exitstatus_returning(code) + saved_exitstatus = $?.nil? ? 0 : $?.exitstatus + begin + `ruby -e 'exit #{code.to_i}'` + yield + ensure + `ruby -e 'exit #{saved_exitstatus.to_i}'` + end +end + +def fixture_file(filename) + File.join(File.dirname(__FILE__), 'fixtures', filename) +end + +def assert_success_response(url) + Net::HTTP.get_response(URI.parse(url)) do |response| + assert_equal "200", response.code, + "Expected HTTP response code 200, got #{response.code}" + end +end + +def assert_not_found_response(url) + Net::HTTP.get_response(URI.parse(url)) do |response| + assert_equal "404", response.code, + "Expected HTTP response code 404, got #{response.code}" + end +end + +def assert_file_exists(path) + assert File.exists?(path), %(Expect "#{path}" to be exists.) +end + +def assert_file_not_exists(path) + assert !File.exists?(path), %(Expect "#{path}" to not exists.) +end diff --git a/vendor/gems/paperclip/test/integration_test.rb b/vendor/gems/paperclip/test/integration_test.rb new file mode 100644 index 0000000..f1523ae --- /dev/null +++ b/vendor/gems/paperclip/test/integration_test.rb @@ -0,0 +1,677 @@ +# encoding: utf-8 + +require './test/helper' +require 'open-uri' + +class IntegrationTest < Test::Unit::TestCase + context "Many models at once" do + setup do + rebuild_model + @file = File.new(fixture_file("5k.png"), 'rb') + 300.times do |i| + Dummy.create! :avatar => @file + end + end + + teardown { @file.close } + + should "not exceed the open file limit" do + assert_nothing_raised do + dummies = Dummy.find(:all) + dummies.each { |dummy| dummy.avatar } + end + end + end + + context "An attachment" do + setup do + rebuild_model :styles => { :thumb => "50x50#" } + @dummy = Dummy.new + @file = File.new(fixture_file("5k.png"), 'rb') + @dummy.avatar = @file + assert @dummy.save + end + + teardown { @file.close } + + should "create its thumbnails properly" do + assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:thumb)}"` + end + + context 'reprocessing with unreadable original' do + setup { File.chmod(0000, @dummy.avatar.path) } + + should "not raise an error" do + assert_nothing_raised do + silence_stream(STDERR) do + @dummy.avatar.reprocess! + end + end + end + + should "return false" do + silence_stream(STDERR) do + assert !@dummy.avatar.reprocess! + end + end + + teardown { File.chmod(0644, @dummy.avatar.path) } + end + + context "redefining its attachment styles" do + setup do + Dummy.class_eval do + has_attached_file :avatar, :styles => { :thumb => "150x25#", :dynamic => lambda { |a| '50x50#' } } + end + @d2 = Dummy.find(@dummy.id) + @original_timestamp = @d2.avatar_updated_at + @d2.avatar.reprocess! + @d2.save + end + + should "create its thumbnails properly" do + assert_match /\b150x25\b/, `identify "#{@dummy.avatar.path(:thumb)}"` + assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:dynamic)}"` + end + + should "change the timestamp" do + assert_not_equal @original_timestamp, @d2.avatar_updated_at + end + end + end + + context "Attachment" do + setup do + @thumb_path = "tmp/public/system/dummies/avatars/000/000/001/thumb/5k.png" + File.delete(@thumb_path) if File.exists?(@thumb_path) + rebuild_model :styles => { :thumb => "50x50#" } + @dummy = Dummy.new + @file = File.new(fixture_file("5k.png"), 'rb') + + end + + teardown { @file.close } + + should "not create the thumbnails upon saving when post-processing is disabled" do + @dummy.avatar.post_processing = false + @dummy.avatar = @file + assert @dummy.save + assert_file_not_exists @thumb_path + end + + should "create the thumbnails upon saving when post_processing is enabled" do + @dummy.avatar.post_processing = true + @dummy.avatar = @file + assert @dummy.save + assert_file_exists @thumb_path + end + end + + context "Attachment with no generated thumbnails" do + setup do + @thumb_small_path = "tmp/public/system/dummies/avatars/000/000/001/thumb_small/5k.png" + @thumb_large_path = "tmp/public/system/dummies/avatars/000/000/001/thumb_large/5k.png" + File.delete(@thumb_small_path) if File.exists?(@thumb_small_path) + File.delete(@thumb_large_path) if File.exists?(@thumb_large_path) + rebuild_model :styles => { :thumb_small => "50x50#", :thumb_large => "60x60#" } + @dummy = Dummy.new + @file = File.new(fixture_file("5k.png"), 'rb') + + @dummy.avatar.post_processing = false + @dummy.avatar = @file + assert @dummy.save + @dummy.avatar.post_processing = true + end + + teardown { @file.close } + + should "allow us to create all thumbnails in one go" do + assert_file_not_exists(@thumb_small_path) + assert_file_not_exists(@thumb_large_path) + + @dummy.avatar.reprocess! + + assert_file_exists(@thumb_small_path) + assert_file_exists(@thumb_large_path) + end + + should "allow us to selectively create each thumbnail" do + assert_file_not_exists(@thumb_small_path) + assert_file_not_exists(@thumb_large_path) + + @dummy.avatar.reprocess! :thumb_small + assert_file_exists(@thumb_small_path) + assert_file_not_exists(@thumb_large_path) + + @dummy.avatar.reprocess! :thumb_large + assert_file_exists(@thumb_large_path) + end + end + + context "A model that modifies its original" do + setup do + rebuild_model :styles => { :original => "2x2#" } + @dummy = Dummy.new + @file = File.new(fixture_file("5k.png"), 'rb') + @dummy.avatar = @file + end + + should "report the file size of the processed file and not the original" do + assert_not_equal File.size(@file.path), @dummy.avatar.size + end + + teardown { @file.close } + end + + context "A model with attachments scoped under an id" do + setup do + rebuild_model :styles => { :large => "100x100", + :medium => "50x50" }, + :path => ":rails_root/tmp/:id/:attachments/:style.:extension" + @dummy = Dummy.new + @file = File.new(fixture_file("5k.png"), 'rb') + @dummy.avatar = @file + end + + teardown { @file.close } + + context "when saved" do + setup do + @dummy.save + @saved_path = @dummy.avatar.path(:large) + end + + should "have a large file in the right place" do + assert_file_exists(@dummy.avatar.path(:large)) + end + + context "and deleted" do + setup do + @dummy.avatar.clear + @dummy.save + end + + should "not have a large file in the right place anymore" do + assert_file_not_exists(@saved_path) + end + + should "not have its next two parent directories" do + assert_file_not_exists(File.dirname(@saved_path)) + assert_file_not_exists(File.dirname(File.dirname(@saved_path))) + end + + before_should "not die if an unexpected SystemCallError happens" do + FileUtils.stubs(:rmdir).raises(Errno::EPIPE) + end + end + end + end + + context "A model with no convert_options setting" do + setup do + rebuild_model :styles => { :large => "300x300>", + :medium => "100x100", + :thumb => ["32x32#", :gif] }, + :default_style => :medium, + :url => "/:attachment/:class/:style/:id/:basename.:extension", + :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension" + @dummy = Dummy.new + end + + should "have its definition return nil when asked about convert_options" do + assert ! Dummy.attachment_definitions[:avatar][:convert_options] + end + + context "redefined to have convert_options setting" do + setup do + rebuild_model :styles => { :large => "300x300>", + :medium => "100x100", + :thumb => ["32x32#", :gif] }, + :convert_options => "-strip -depth 8", + :default_style => :medium, + :url => "/:attachment/:class/:style/:id/:basename.:extension", + :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension" + end + + should "have its definition return convert_options value when asked about convert_options" do + assert_equal "-strip -depth 8", Dummy.attachment_definitions[:avatar][:convert_options] + end + end + end + + context "A model with no source_file_options setting" do + setup do + rebuild_model :styles => { :large => "300x300>", + :medium => "100x100", + :thumb => ["32x32#", :gif] }, + :default_style => :medium, + :url => "/:attachment/:class/:style/:id/:basename.:extension", + :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension" + @dummy = Dummy.new + end + + should "have its definition return nil when asked about source_file_options" do + assert ! Dummy.attachment_definitions[:avatar][:source_file_options] + end + + context "redefined to have source_file_options setting" do + setup do + rebuild_model :styles => { :large => "300x300>", + :medium => "100x100", + :thumb => ["32x32#", :gif] }, + :source_file_options => "-density 400", + :default_style => :medium, + :url => "/:attachment/:class/:style/:id/:basename.:extension", + :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension" + end + + should "have its definition return source_file_options value when asked about source_file_options" do + assert_equal "-density 400", Dummy.attachment_definitions[:avatar][:source_file_options] + end + end + end + + [000,002,022].each do |umask| + context "when the umask is #{umask}" do + setup do + rebuild_model + @dummy = Dummy.new + @file = File.new(fixture_file("5k.png"), 'rb') + @umask = File.umask(umask) + end + + teardown do + File.umask @umask + @file.close + end + + should "respect the current umask" do + @dummy.avatar = @file + @dummy.save + assert_equal 0666&~umask, 0666&File.stat(@dummy.avatar.path).mode + end + end + end + + context "A model with a filesystem attachment" do + setup do + rebuild_model :styles => { :large => "300x300>", + :medium => "100x100", + :thumb => ["32x32#", :gif] }, + :default_style => :medium, + :url => "/:attachment/:class/:style/:id/:basename.:extension", + :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension" + @dummy = Dummy.new + @file = File.new(fixture_file("5k.png"), 'rb') + @bad_file = File.new(fixture_file("bad.png"), 'rb') + + assert @dummy.avatar = @file + assert @dummy.valid?, @dummy.errors.full_messages.join(", ") + assert @dummy.save + end + + teardown { [@file, @bad_file].each(&:close) } + + should "write and delete its files" do + [["434x66", :original], + ["300x46", :large], + ["100x15", :medium], + ["32x32", :thumb]].each do |geo, style| + cmd = %Q[identify -format "%wx%h" "#{@dummy.avatar.path(style)}"] + assert_equal geo, `#{cmd}`.chomp, cmd + end + + saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) } + + @d2 = Dummy.find(@dummy.id) + assert_equal "100x15", `identify -format "%wx%h" "#{@d2.avatar.path}"`.chomp + assert_equal "434x66", `identify -format "%wx%h" "#{@d2.avatar.path(:original)}"`.chomp + assert_equal "300x46", `identify -format "%wx%h" "#{@d2.avatar.path(:large)}"`.chomp + assert_equal "100x15", `identify -format "%wx%h" "#{@d2.avatar.path(:medium)}"`.chomp + assert_equal "32x32", `identify -format "%wx%h" "#{@d2.avatar.path(:thumb)}"`.chomp + + assert @dummy.valid? + assert @dummy.save + + saved_paths.each do |p| + assert_file_exists(p) + end + + @dummy.avatar.clear + assert_nil @dummy.avatar_file_name + assert @dummy.valid? + assert @dummy.save + + saved_paths.each do |p| + assert_file_not_exists(p) + end + + @d2 = Dummy.find(@dummy.id) + assert_nil @d2.avatar_file_name + end + + should "work exactly the same when new as when reloaded" do + @d2 = Dummy.find(@dummy.id) + + assert_equal @dummy.avatar_file_name, @d2.avatar_file_name + [:thumb, :medium, :large, :original].each do |style| + assert_equal @dummy.avatar.path(style), @d2.avatar.path(style) + end + + saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) } + + @d2.avatar.clear + assert @d2.save + + saved_paths.each do |p| + assert_file_not_exists(p) + end + end + + should "not abide things that don't have adapters" do + assert_raises(Paperclip::AdapterRegistry::NoHandlerError) do + @dummy.avatar = "not a file" + end + end + + should "not be ok with bad files" do + @dummy.avatar = @bad_file + assert ! @dummy.valid? + end + + should "know the difference between good files, bad files, and not files when validating" do + Dummy.validates_attachment_presence :avatar + @d2 = Dummy.find(@dummy.id) + @d2.avatar = @file + assert @d2.valid?, @d2.errors.full_messages.inspect + @d2.avatar = @bad_file + assert ! @d2.valid? + end + + should "be able to reload without saving and not have the file disappear" do + @dummy.avatar = @file + assert @dummy.save, @dummy.errors.full_messages.inspect + @dummy.avatar.clear + assert_nil @dummy.avatar_file_name + @dummy.reload + assert_equal "5k.png", @dummy.avatar_file_name + end + + context "that is assigned its file from another Paperclip attachment" do + setup do + @dummy2 = Dummy.new + @file2 = File.new(fixture_file("12k.png"), 'rb') + assert @dummy2.avatar = @file2 + @dummy2.save + end + + teardown { @file2.close } + + should "work when assigned a file" do + assert_not_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`, + `identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"` + + assert @dummy.avatar = @dummy2.avatar + @dummy.save + assert_equal @dummy.avatar_file_name, @dummy2.avatar_file_name + assert_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`, + `identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"` + end + end + + end + + context "A model with an attachments association and a Paperclip attachment" do + setup do + Dummy.class_eval do + has_many :attachments, :class_name => 'Dummy' + end + + @file = File.new(fixture_file("5k.png"), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + should "should not error when saving" do + @dummy.save! + end + end + + context "A model with an attachment with hash in file name" do + setup do + @settings = { :styles => { :thumb => "50x50#" }, + :path => ":rails_root/public/system/:attachment/:id_partition/:style/:hash.:extension", + :url => "/system/:attachment/:id_partition/:style/:hash.:extension", + :hash_secret => "somesecret" } + + rebuild_model @settings + + @file = File.new(fixture_file("5k.png"), 'rb') + @dummy = Dummy.create! :avatar => @file + end + + teardown do + @file.close + end + + should "be accessible" do + assert_file_exists(@dummy.avatar.path(:original)) + assert_file_exists(@dummy.avatar.path(:thumb)) + end + + context "when new style is added" do + setup do + @dummy.avatar.options[:styles][:mini] = "25x25#" + @dummy.avatar.instance_variable_set :@normalized_styles, nil + @dummy.avatar.reprocess! 'mini' + end + + should "make all the styles accessible" do + assert_file_exists(@dummy.avatar.path(:original)) + assert_file_exists(@dummy.avatar.path(:thumb)) + assert_file_exists(@dummy.avatar.path(:mini)) + end + end + end + + if ENV['S3_BUCKET'] + def s3_files_for attachment + [:thumb, :medium, :large, :original].inject({}) do |files, style| + data = `curl "#{attachment.url(style)}" 2>/dev/null`.chomp + t = Tempfile.new("paperclip-test") + t.binmode + t.write(data) + t.rewind + files[style] = t + files + end + end + + def s3_headers_for attachment, style + `curl --head "#{attachment.url(style)}" 2>/dev/null`.split("\n").inject({}) do |h,head| + split_head = head.chomp.split(/\s*:\s*/, 2) + h[split_head.first.downcase] = split_head.last unless split_head.empty? + h + end + end + + context "A model with an S3 attachment" do + setup do + rebuild_model :styles => { :large => "300x300>", + :medium => "100x100", + :thumb => ["32x32#", :gif] }, + :storage => :s3, + :s3_credentials => File.new(fixture_file('s3.yml')), + :s3_options => { :logger => Paperclip.logger }, + :default_style => :medium, + :bucket => ENV['S3_BUCKET'], + :path => ":class/:attachment/:id/:style/:basename.:extension" + + @dummy = Dummy.new + @file = File.new(fixture_file('5k.png'), 'rb') + @bad_file = File.new(fixture_file('bad.png'), 'rb') + + @dummy.avatar = @file + @dummy.valid? + @dummy.save! + + @files_on_s3 = s3_files_for(@dummy.avatar) + end + + teardown do + @file.close + @bad_file.close + @files_on_s3.values.each(&:close) if @files_on_s3 + end + + context 'assigning itself to a new model' do + setup do + @d2 = Dummy.new + @d2.avatar = @dummy.avatar + @d2.save + end + + should "have the same name as the old file" do + assert_equal @d2.avatar.original_filename, @dummy.avatar.original_filename + end + end + + should "have the same contents as the original" do + assert_equal @file.read, @files_on_s3[:original].read + end + + should "write and delete its files" do + [["434x66", :original], + ["300x46", :large], + ["100x15", :medium], + ["32x32", :thumb]].each do |geo, style| + cmd = %Q[identify -format "%wx%h" "#{@files_on_s3[style].path}"] + assert_equal geo, `#{cmd}`.chomp, cmd + end + + @d2 = Dummy.find(@dummy.id) + @d2_files = s3_files_for @d2.avatar + [["434x66", :original], + ["300x46", :large], + ["100x15", :medium], + ["32x32", :thumb]].each do |geo, style| + cmd = %Q[identify -format "%wx%h" "#{@d2_files[style].path}"] + assert_equal geo, `#{cmd}`.chomp, cmd + end + + @dummy.avatar.clear + assert_nil @dummy.avatar_file_name + assert @dummy.valid? + assert @dummy.save + + [:thumb, :medium, :large, :original].each do |style| + assert ! @dummy.avatar.exists?(style) + end + + @d2 = Dummy.find(@dummy.id) + assert_nil @d2.avatar_file_name + end + + should "work exactly the same when new as when reloaded" do + @d2 = Dummy.find(@dummy.id) + + assert_equal @dummy.avatar_file_name, @d2.avatar_file_name + + [:thumb, :medium, :large, :original].each do |style| + begin + first_file = open(@dummy.avatar.url(style)) + second_file = open(@dummy.avatar.url(style)) + assert_equal first_file.read, second_file.read + ensure + first_file.close if first_file + second_file.close if second_file + end + end + + @d2.avatar.clear + assert @d2.save + + [:thumb, :medium, :large, :original].each do |style| + assert ! @dummy.avatar.exists?(style) + end + end + + should "know the difference between good files, bad files, and nil" do + @dummy.avatar = @bad_file + assert ! @dummy.valid? + @dummy.avatar = nil + assert @dummy.valid? + + Dummy.validates_attachment_presence :avatar + @d2 = Dummy.find(@dummy.id) + @d2.avatar = @file + assert @d2.valid? + @d2.avatar = @bad_file + assert ! @d2.valid? + @d2.avatar = nil + assert ! @d2.valid? + end + + should "be able to reload without saving and not have the file disappear" do + @dummy.avatar = @file + assert @dummy.save + @dummy.avatar = nil + assert_nil @dummy.avatar_file_name + @dummy.reload + assert_equal "5k.png", @dummy.avatar_file_name + end + + should "have the right content type" do + headers = s3_headers_for(@dummy.avatar, :original) + assert_equal 'image/png', headers['content-type'] + end + + context "with non-english character in the file name" do + setup do + @file.stubs(:original_filename).returns("クリップ.png") + @dummy.avatar = @file + end + + should "not raise any error" do + @dummy.save! + end + end + end + end + + context "Copying attachments between models" do + setup do + rebuild_model + @file = File.new(fixture_file("5k.png"), 'rb') + end + + teardown { @file.close } + + should "succeed when original attachment is a file" do + original = Dummy.new + original.avatar = @file + assert original.save + + copy = Dummy.new + copy.avatar = original.avatar + assert copy.save + + assert copy.avatar.present? + end + + should "succeed when original attachment is empty" do + original = Dummy.create! + + copy = Dummy.new + copy.avatar = @file + assert copy.save + assert copy.avatar.present? + + copy.avatar = original.avatar + assert copy.save + assert !copy.avatar.present? + end + end +end diff --git a/vendor/gems/paperclip/test/interpolations_test.rb b/vendor/gems/paperclip/test/interpolations_test.rb new file mode 100644 index 0000000..6c7f4af --- /dev/null +++ b/vendor/gems/paperclip/test/interpolations_test.rb @@ -0,0 +1,238 @@ +require './test/helper' + +class InterpolationsTest < Test::Unit::TestCase + should "return all methods but the infrastructure when sent #all" do + methods = Paperclip::Interpolations.all + assert ! methods.include?(:[]) + assert ! methods.include?(:[]=) + assert ! methods.include?(:all) + methods.each do |m| + assert Paperclip::Interpolations.respond_to?(m) + end + end + + should "return the Rails.root" do + assert_equal Rails.root, Paperclip::Interpolations.rails_root(:attachment, :style) + end + + should "return the Rails.env" do + assert_equal Rails.env, Paperclip::Interpolations.rails_env(:attachment, :style) + end + + should "return the class of the Interpolations module when called with no params" do + assert_equal Module, Paperclip::Interpolations.class + end + + should "return the class of the instance" do + attachment = mock + attachment.expects(:instance).returns(attachment) + attachment.expects(:class).returns("Thing") + assert_equal "things", Paperclip::Interpolations.class(attachment, :style) + end + + should "return the basename of the file" do + attachment = mock + attachment.expects(:original_filename).returns("one.jpg").times(2) + assert_equal "one", Paperclip::Interpolations.basename(attachment, :style) + end + + should "return the extension of the file" do + attachment = mock + attachment.expects(:original_filename).returns("one.jpg") + attachment.expects(:styles).returns({}) + assert_equal "jpg", Paperclip::Interpolations.extension(attachment, :style) + end + + should "return the extension of the file as the format if defined in the style" do + attachment = mock + attachment.expects(:original_filename).never + attachment.expects(:styles).twice.returns({:style => {:format => "png"}}) + + [:style, 'style'].each do |style| + assert_equal "png", Paperclip::Interpolations.extension(attachment, style) + end + end + + should "return the extension of the file based on the content type" do + attachment = mock + attachment.expects(:content_type).returns('image/jpeg') + attachment.expects(:styles).returns({}) + interpolations = Paperclip::Interpolations + interpolations.expects(:extension).returns('random') + assert_equal "jpeg", interpolations.content_type_extension(attachment, :style) + end + + should "return the original extension of the file if it matches a content type extension" do + attachment = mock + attachment.expects(:content_type).returns('image/jpeg') + attachment.expects(:styles).returns({}) + interpolations = Paperclip::Interpolations + interpolations.expects(:extension).returns('jpe') + assert_equal "jpe", interpolations.content_type_extension(attachment, :style) + end + + should "return the latter half of the content type of the extension if no match found" do + attachment = mock + attachment.expects(:content_type).at_least_once().returns('not/found') + attachment.expects(:styles).returns({}) + interpolations = Paperclip::Interpolations + interpolations.expects(:extension).returns('random') + assert_equal "found", interpolations.content_type_extension(attachment, :style) + end + + should "return the format if defined in the style, ignoring the content type" do + attachment = mock + attachment.expects(:content_type).returns('image/jpeg') + attachment.expects(:styles).returns({:style => {:format => "png"}}) + interpolations = Paperclip::Interpolations + interpolations.expects(:extension).returns('random') + assert_equal "png", interpolations.content_type_extension(attachment, :style) + end + + should "be able to handle numeric style names" do + attachment = mock( + :styles => {:"4" => {:format => :expected_extension}} + ) + assert_equal :expected_extension, Paperclip::Interpolations.extension(attachment, 4) + end + + should "return the #to_param of the attachment" do + attachment = mock + attachment.expects(:to_param).returns("23-awesome") + attachment.expects(:instance).returns(attachment) + assert_equal "23-awesome", Paperclip::Interpolations.param(attachment, :style) + end + + should "return the id of the attachment" do + attachment = mock + attachment.expects(:id).returns(23) + attachment.expects(:instance).returns(attachment) + assert_equal 23, Paperclip::Interpolations.id(attachment, :style) + end + + should "return nil for attachments to new records" do + attachment = mock + attachment.expects(:id).returns(nil) + attachment.expects(:instance).returns(attachment) + assert_nil Paperclip::Interpolations.id(attachment, :style) + end + + should "return the partitioned id of the attachment when the id is an integer" do + attachment = mock + attachment.expects(:id).returns(23) + attachment.expects(:instance).returns(attachment) + assert_equal "000/000/023", Paperclip::Interpolations.id_partition(attachment, :style) + end + + should "return the partitioned id of the attachment when the id is a string" do + attachment = mock + attachment.expects(:id).returns("32fnj23oio2f") + attachment.expects(:instance).returns(attachment) + assert_equal "32f/nj2/3oi", Paperclip::Interpolations.id_partition(attachment, :style) + end + + should "return nil for the partitioned id of an attachment to a new record (when the id is nil)" do + attachment = mock + attachment.expects(:id).returns(nil) + attachment.expects(:instance).returns(attachment) + assert_nil Paperclip::Interpolations.id_partition(attachment, :style) + end + + should "return the name of the attachment" do + attachment = mock + attachment.expects(:name).returns("file") + assert_equal "files", Paperclip::Interpolations.attachment(attachment, :style) + end + + should "return the style" do + assert_equal :style, Paperclip::Interpolations.style(:attachment, :style) + end + + should "return the default style" do + attachment = mock + attachment.expects(:default_style).returns(:default_style) + assert_equal :default_style, Paperclip::Interpolations.style(attachment, nil) + end + + should "reinterpolate :url" do + attachment = mock + attachment.expects(:url).with(:style, :timestamp => false, :escape => false).returns("1234") + assert_equal "1234", Paperclip::Interpolations.url(attachment, :style) + end + + should "raise if infinite loop detcted reinterpolating :url" do + attachment = Object.new + class << attachment + def url(*args) + Paperclip::Interpolations.url(self, :style) + end + end + assert_raises(Paperclip::Errors::InfiniteInterpolationError){ Paperclip::Interpolations.url(attachment, :style) } + end + + should "return the filename as basename.extension" do + attachment = mock + attachment.expects(:styles).returns({}) + attachment.expects(:original_filename).returns("one.jpg").times(3) + assert_equal "one.jpg", Paperclip::Interpolations.filename(attachment, :style) + end + + should "return the filename as basename.extension when format supplied" do + attachment = mock + attachment.expects(:styles).returns({:style => {:format => :png}}) + attachment.expects(:original_filename).returns("one.jpg").times(2) + assert_equal "one.png", Paperclip::Interpolations.filename(attachment, :style) + end + + should "return the filename as basename when extension is blank" do + attachment = mock + attachment.stubs(:styles).returns({}) + attachment.stubs(:original_filename).returns("one") + assert_equal "one", Paperclip::Interpolations.filename(attachment, :style) + end + + should "return the basename when the extension contains regexp special characters" do + attachment = mock + attachment.stubs(:styles).returns({}) + attachment.stubs(:original_filename).returns("one.ab)") + assert_equal "one", Paperclip::Interpolations.basename(attachment, :style) + end + + should "return the timestamp" do + now = Time.now + zone = 'UTC' + attachment = mock + attachment.expects(:instance_read).with(:updated_at).returns(now) + attachment.expects(:time_zone).returns(zone) + assert_equal now.in_time_zone(zone).to_s, Paperclip::Interpolations.timestamp(attachment, :style) + end + + should "return updated_at" do + attachment = mock + seconds_since_epoch = 1234567890 + attachment.expects(:updated_at).returns(seconds_since_epoch) + assert_equal seconds_since_epoch, Paperclip::Interpolations.updated_at(attachment, :style) + end + + should "return attachment's hash when passing both arguments" do + attachment = mock + fake_hash = "a_wicked_secure_hash" + attachment.expects(:hash_key).returns(fake_hash) + assert_equal fake_hash, Paperclip::Interpolations.hash(attachment, :style) + end + + should "return Object#hash when passing no argument" do + attachment = mock + fake_hash = "a_wicked_secure_hash" + attachment.expects(:hash_key).never.returns(fake_hash) + assert_not_equal fake_hash, Paperclip::Interpolations.hash + end + + should "call all expected interpolations with the given arguments" do + Paperclip::Interpolations.expects(:id).with(:attachment, :style).returns(1234) + Paperclip::Interpolations.expects(:attachment).with(:attachment, :style).returns("attachments") + Paperclip::Interpolations.expects(:notreal).never + value = Paperclip::Interpolations.interpolate(":notreal/:id/:attachment", :attachment, :style) + assert_equal ":notreal/1234/attachments", value + end +end diff --git a/vendor/gems/paperclip/test/io_adapters/abstract_adapter_test.rb b/vendor/gems/paperclip/test/io_adapters/abstract_adapter_test.rb new file mode 100644 index 0000000..a00385a --- /dev/null +++ b/vendor/gems/paperclip/test/io_adapters/abstract_adapter_test.rb @@ -0,0 +1,43 @@ +require './test/helper' + +class AbstractAdapterTest < Test::Unit::TestCase + class TestAdapter < Paperclip::AbstractAdapter + attr_accessor :original_file_name, :tempfile + + def content_type + Paperclip::ContentTypeDetector.new(path).detect + end + end + + context "content type from file command" do + setup do + @adapter = TestAdapter.new + @adapter.stubs(:path).returns("image.png") + end + + should "return the content type without newline" do + assert_equal "image/png", @adapter.content_type + end + end + + context "nil?" do + should "return false" do + assert !TestAdapter.new.nil? + end + end + + context "delegation" do + setup do + @adapter = TestAdapter.new + @adapter.tempfile = stub("Tempfile") + end + + [:close, :closed?, :eof?, :path, :rewind, :unlink].each do |method| + should "delegate #{method} to @tempfile" do + @adapter.tempfile.stubs(method) + @adapter.public_send(method) + assert_received @adapter.tempfile, method + end + end + end +end diff --git a/vendor/gems/paperclip/test/io_adapters/attachment_adapter_test.rb b/vendor/gems/paperclip/test/io_adapters/attachment_adapter_test.rb new file mode 100644 index 0000000..41cb758 --- /dev/null +++ b/vendor/gems/paperclip/test/io_adapters/attachment_adapter_test.rb @@ -0,0 +1,113 @@ +require './test/helper' + +class AttachmentAdapterTest < Test::Unit::TestCase + + def setup + rebuild_model :path => "tmp/:class/:attachment/:style/:filename", :styles => {:thumb => '50x50'} + @attachment = Dummy.new.avatar + end + + context "for an attachment" do + setup do + @file = File.new(fixture_file("5k.png")) + @file.binmode + + @attachment.assign(@file) + @attachment.save + @subject = Paperclip.io_adapters.for(@attachment) + end + + teardown do + @file.close + end + + should "get the right filename" do + assert_equal "5k.png", @subject.original_filename + end + + should "force binmode on tempfile" do + assert @subject.instance_variable_get("@tempfile").binmode? + end + + should "get the content type" do + assert_equal "image/png", @subject.content_type + end + + should "get the file's size" do + assert_equal 4456, @subject.size + end + + should "return false for a call to nil?" do + assert ! @subject.nil? + end + + should "generate a MD5 hash of the contents" do + expected = Digest::MD5.file(@file.path).to_s + assert_equal expected, @subject.fingerprint + end + + should "read the contents of the file" do + expected = @file.read + actual = @subject.read + assert expected.length > 0 + assert_equal expected.length, actual.length + assert_equal expected, actual + end + + end + + context "for a style" do + setup do + @file = File.new(fixture_file("5k.png")) + @file.binmode + + @attachment.assign(@file) + + @thumb = Tempfile.new("thumbnail").tap(&:binmode) + FileUtils.cp @attachment.queued_for_write[:thumb].path, @thumb.path + + @attachment.save + @subject = Paperclip.io_adapters.for(@attachment.styles[:thumb]) + end + + teardown do + @file.close + @thumb.close + end + + should "get the original filename" do + assert_equal "5k.png", @subject.original_filename + end + + should "force binmode on tempfile" do + assert @subject.instance_variable_get("@tempfile").binmode? + end + + should "get the content type" do + assert_equal "image/png", @subject.content_type + end + + should "get the thumbnail's file size" do + assert_equal @thumb.size, @subject.size + end + + should "return false for a call to nil?" do + assert ! @subject.nil? + end + + should "generate a MD5 hash of the contents" do + expected = Digest::MD5.file(@thumb.path).to_s + assert_equal expected, @subject.fingerprint + end + + should "read the contents of the thumbnail" do + @thumb.rewind + expected = @thumb.read + actual = @subject.read + assert expected.length > 0 + assert_equal expected.length, actual.length + assert_equal expected, actual + end + + end +end diff --git a/vendor/gems/paperclip/test/io_adapters/file_adapter_test.rb b/vendor/gems/paperclip/test/io_adapters/file_adapter_test.rb new file mode 100644 index 0000000..1f44629 --- /dev/null +++ b/vendor/gems/paperclip/test/io_adapters/file_adapter_test.rb @@ -0,0 +1,100 @@ +require './test/helper' + +class FileAdapterTest < Test::Unit::TestCase + context "a new instance" do + context "with normal file" do + setup do + @file = File.new(fixture_file("5k.png")) + @file.binmode + @subject = Paperclip.io_adapters.for(@file) + end + + teardown { @file.close } + + should "get the right filename" do + assert_equal "5k.png", @subject.original_filename + end + + should "force binmode on tempfile" do + assert @subject.instance_variable_get("@tempfile").binmode? + end + + should "get the content type" do + assert_equal "image/png", @subject.content_type + end + + should "return content type as a string" do + assert_kind_of String, @subject.content_type + end + + should "get the file's size" do + assert_equal 4456, @subject.size + end + + should "return false for a call to nil?" do + assert ! @subject.nil? + end + + should "generate a MD5 hash of the contents" do + expected = Digest::MD5.file(@file.path).to_s + assert_equal expected, @subject.fingerprint + end + + should "read the contents of the file" do + expected = @file.read + assert expected.length > 0 + assert_equal expected, @subject.read + end + + context "file with multiple possible content type" do + setup do + MIME::Types.stubs(:type_for).returns([MIME::Type.new('image/x-png'), MIME::Type.new('image/png')]) + end + + should "prefer officially registered mime type" do + assert_equal "image/png", @subject.content_type + end + + should "return content type as a string" do + assert_kind_of String, @subject.content_type + end + end + + context "file with multiple possible x-types but no official type" do + setup do + MIME::Types.stubs(:type_for).returns([MIME::Type.new('image/x-mp4'), MIME::Type.new('image/x-video')]) + @subject = Paperclip.io_adapters.for(@file) + end + + should "return the first" do + assert_equal "image/x-mp4", @subject.content_type + end + end + + context "file with content type derived from file command on *nix" do + setup do + MIME::Types.stubs(:type_for).returns([]) + Paperclip.stubs(:run).returns("application/vnd.ms-office\n") + @subject = Paperclip.io_adapters.for(@file) + end + + should "return content type without newline character" do + assert_equal "application/vnd.ms-office", @subject.content_type + end + end + end + + context "empty file" do + setup do + @file = Tempfile.new("file_adapter_test") + @subject = Paperclip.io_adapters.for(@file) + end + + teardown { @file.close } + + should "provide correct mime-type" do + assert_match %r{.*/x-empty}, @subject.content_type + end + end + end +end diff --git a/vendor/gems/paperclip/test/io_adapters/identity_adapter_test.rb b/vendor/gems/paperclip/test/io_adapters/identity_adapter_test.rb new file mode 100644 index 0000000..ad26b2e --- /dev/null +++ b/vendor/gems/paperclip/test/io_adapters/identity_adapter_test.rb @@ -0,0 +1,8 @@ +require './test/helper' + +class IdentityAdapterTest < Test::Unit::TestCase + should "respond to #new by returning the argument" do + adapter = Paperclip::IdentityAdapter.new + assert_equal :target, adapter.new(:target) + end +end diff --git a/vendor/gems/paperclip/test/io_adapters/nil_adapter_test.rb b/vendor/gems/paperclip/test/io_adapters/nil_adapter_test.rb new file mode 100644 index 0000000..746d22b --- /dev/null +++ b/vendor/gems/paperclip/test/io_adapters/nil_adapter_test.rb @@ -0,0 +1,25 @@ +require './test/helper' + +class NilAdapterTest < Test::Unit::TestCase + context 'a new instance' do + setup do + @subject = Paperclip.io_adapters.for(nil) + end + + should "get the right filename" do + assert_equal "", @subject.original_filename + end + + should "get the content type" do + assert_equal "", @subject.content_type + end + + should "get the file's size" do + assert_equal 0, @subject.size + end + + should "return true for a call to nil?" do + assert @subject.nil? + end + end +end diff --git a/vendor/gems/paperclip/test/io_adapters/registry_test.rb b/vendor/gems/paperclip/test/io_adapters/registry_test.rb new file mode 100644 index 0000000..6ead08a --- /dev/null +++ b/vendor/gems/paperclip/test/io_adapters/registry_test.rb @@ -0,0 +1,32 @@ +require './test/helper' + +class AdapterRegistryTest < Test::Unit::TestCase + context "for" do + setup do + class AdapterTest + def initialize(target); end + end + @subject = Paperclip::AdapterRegistry.new + @subject.register(AdapterTest){|t| Symbol === t } + end + should "return the class registered for the adapted type" do + assert_equal AdapterTest, @subject.for(:target).class + end + end + + context "registered?" do + setup do + class AdapterTest + def initialize(target); end + end + @subject = Paperclip::AdapterRegistry.new + @subject.register(AdapterTest){|t| Symbol === t } + end + should "return true when the class of this adapter has been registered" do + assert @subject.registered?(AdapterTest.new(:target)) + end + should "return false when the adapter has not been registered" do + assert ! @subject.registered?(Object) + end + end +end diff --git a/vendor/gems/paperclip/test/io_adapters/stringio_adapter_test.rb b/vendor/gems/paperclip/test/io_adapters/stringio_adapter_test.rb new file mode 100644 index 0000000..27083e4 --- /dev/null +++ b/vendor/gems/paperclip/test/io_adapters/stringio_adapter_test.rb @@ -0,0 +1,51 @@ +require './test/helper' + +class StringioFileProxyTest < Test::Unit::TestCase + context "a new instance" do + setup do + @contents = "abc123" + @stringio = StringIO.new(@contents) + @subject = Paperclip.io_adapters.for(@stringio) + end + + should "return a file name" do + assert_equal "stringio.txt", @subject.original_filename + end + + should "return a content type" do + assert_equal "text/plain", @subject.content_type + end + + should "return the size of the data" do + assert_equal 6, @subject.size + end + + should "generate an MD5 hash of the contents" do + assert_equal Digest::MD5.hexdigest(@contents), @subject.fingerprint + end + + should "generate correct fingerprint after read" do + fingerprint = Digest::MD5.hexdigest(@subject.read) + assert_equal fingerprint, @subject.fingerprint + end + + should "generate same fingerprint" do + assert_equal @subject.fingerprint, @subject.fingerprint + end + + should "return the data contained in the StringIO" do + assert_equal "abc123", @subject.read + end + + should 'accept a content_type' do + @subject.content_type = 'image/png' + assert_equal 'image/png', @subject.content_type + end + + should 'accept an orgiginal_filename' do + @subject.original_filename = 'image.png' + assert_equal 'image.png', @subject.original_filename + end + + end +end diff --git a/vendor/gems/paperclip/test/io_adapters/uploaded_file_adapter_test.rb b/vendor/gems/paperclip/test/io_adapters/uploaded_file_adapter_test.rb new file mode 100644 index 0000000..7660ef9 --- /dev/null +++ b/vendor/gems/paperclip/test/io_adapters/uploaded_file_adapter_test.rb @@ -0,0 +1,99 @@ +require './test/helper' + +class UploadedFileAdapterTest < Test::Unit::TestCase + context "a new instance" do + context "with UploadedFile responding to #tempfile" do + setup do + class UploadedFile < OpenStruct; end + tempfile = File.new(fixture_file("5k.png")) + tempfile.binmode + + @file = UploadedFile.new( + :original_filename => "5k.png", + :content_type => "image/png", + :head => "", + :tempfile => tempfile, + :path => tempfile.path + ) + @subject = Paperclip.io_adapters.for(@file) + end + + should "get the right filename" do + assert_equal "5k.png", @subject.original_filename + end + + should "force binmode on tempfile" do + assert @subject.instance_variable_get("@tempfile").binmode? + end + + should "get the content type" do + assert_equal "image/png", @subject.content_type + end + + should "get the file's size" do + assert_equal 4456, @subject.size + end + + should "return false for a call to nil?" do + assert ! @subject.nil? + end + + should "generate a MD5 hash of the contents" do + expected = Digest::MD5.file(@file.tempfile.path).to_s + assert_equal expected, @subject.fingerprint + end + + should "read the contents of the file" do + expected = @file.tempfile.read + assert expected.length > 0 + assert_equal expected, @subject.read + end + end + + context "with UploadFile responding to #path" do + setup do + class UploadedFile < OpenStruct; end + @file = UploadedFile.new( + :original_filename => "5k.png", + :content_type => "image/png", + :head => "", + :path => fixture_file("5k.png") + ) + @subject = Paperclip.io_adapters.for(@file) + end + + should "get the right filename" do + assert_equal "5k.png", @subject.original_filename + end + + should "force binmode on tempfile" do + assert @subject.instance_variable_get("@tempfile").binmode? + end + + should "get the content type" do + assert_equal "image/png", @subject.content_type + end + + should "get the file's size" do + assert_equal 4456, @subject.size + end + + should "return false for a call to nil?" do + assert ! @subject.nil? + end + + should "generate a MD5 hash of the contents" do + expected = Digest::MD5.file(@file.path).to_s + assert_equal expected, @subject.fingerprint + end + + should "read the contents of the file" do + expected_file = File.new(@file.path) + expected_file.binmode + expected = expected_file.read + assert expected.length > 0 + assert_equal expected, @subject.read + end + end + end +end diff --git a/vendor/gems/paperclip/test/io_adapters/uri_adapter_test.rb b/vendor/gems/paperclip/test/io_adapters/uri_adapter_test.rb new file mode 100644 index 0000000..2a58ab8 --- /dev/null +++ b/vendor/gems/paperclip/test/io_adapters/uri_adapter_test.rb @@ -0,0 +1,82 @@ +require './test/helper' + +class UriProxyTest < Test::Unit::TestCase + context "a new instance" do + setup do + @open_return = StringIO.new("xxx") + @open_return.stubs(:content_type).returns("image/png") + Paperclip::UriAdapter.any_instance.stubs(:download_content).returns(@open_return) + @uri = URI.parse("http://thoughtbot.com/images/thoughtbot-logo.png") + @subject = Paperclip.io_adapters.for(@uri) + end + + should "return a file name" do + assert_equal "thoughtbot-logo.png", @subject.original_filename + end + + should "return a content type" do + assert_equal "image/png", @subject.content_type + end + + should "return the size of the data" do + assert_equal @open_return.size, @subject.size + end + + should "generate an MD5 hash of the contents" do + assert_equal Digest::MD5.hexdigest("xxx"), @subject.fingerprint + end + + should "generate correct fingerprint after read" do + fingerprint = Digest::MD5.hexdigest(@subject.read) + assert_equal fingerprint, @subject.fingerprint + end + + should "generate same fingerprint" do + assert_equal @subject.fingerprint, @subject.fingerprint + end + + should "return the data contained in the StringIO" do + assert_equal "xxx", @subject.read + end + + should 'accept a content_type' do + @subject.content_type = 'image/png' + assert_equal 'image/png', @subject.content_type + end + + should 'accept an orgiginal_filename' do + @subject.original_filename = 'image.png' + assert_equal 'image.png', @subject.original_filename + end + + end + + context "a directory index url" do + setup do + Paperclip::UriAdapter.any_instance.stubs(:download_content).returns(StringIO.new("xxx")) + @uri = URI.parse("http://thoughtbot.com") + @subject = Paperclip.io_adapters.for(@uri) + end + + should "return a file name" do + assert_equal "index.html", @subject.original_filename + end + + should "return a content type" do + assert_equal "text/html", @subject.content_type + end + end + + context "a url with query params" do + setup do + Paperclip::UriAdapter.any_instance.stubs(:download_content).returns(StringIO.new("xxx")) + @uri = URI.parse("https://github.com/thoughtbot/paperclip?file=test") + @subject = Paperclip.io_adapters.for(@uri) + end + + should "return a file name" do + assert_equal "paperclip", @subject.original_filename + end + end + +end diff --git a/vendor/gems/paperclip/test/matchers/have_attached_file_matcher_test.rb b/vendor/gems/paperclip/test/matchers/have_attached_file_matcher_test.rb new file mode 100644 index 0000000..3e0821c --- /dev/null +++ b/vendor/gems/paperclip/test/matchers/have_attached_file_matcher_test.rb @@ -0,0 +1,24 @@ +require './test/helper' + +class HaveAttachedFileMatcherTest < Test::Unit::TestCase + context "have_attached_file" do + setup do + @dummy_class = reset_class "Dummy" + reset_table "dummies" + @matcher = self.class.have_attached_file(:avatar) + end + + context "given a class with no attachment" do + should_reject_dummy_class + end + + context "given a class with an attachment" do + setup do + modify_table("dummies"){|d| d.string :avatar_file_name } + @dummy_class.has_attached_file :avatar + end + + should_accept_dummy_class + end + end +end diff --git a/vendor/gems/paperclip/test/matchers/validate_attachment_content_type_matcher_test.rb b/vendor/gems/paperclip/test/matchers/validate_attachment_content_type_matcher_test.rb new file mode 100644 index 0000000..35d9a15 --- /dev/null +++ b/vendor/gems/paperclip/test/matchers/validate_attachment_content_type_matcher_test.rb @@ -0,0 +1,110 @@ +require './test/helper' + +class ValidateAttachmentContentTypeMatcherTest < Test::Unit::TestCase + context "validate_attachment_content_type" do + setup do + reset_table("dummies") do |d| + d.string :title + d.string :avatar_file_name + d.string :avatar_content_type + end + @dummy_class = reset_class "Dummy" + @dummy_class.has_attached_file :avatar + @matcher = self.class.validate_attachment_content_type(:avatar). + allowing(%w(image/png image/jpeg)). + rejecting(%w(audio/mp3 application/octet-stream)) + end + + context "given a class with no validation" do + should_reject_dummy_class + end + + context "given a class with a validation that doesn't match" do + setup do + @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{audio/.*} + end + + should_reject_dummy_class + end + + context "given a class with a matching validation" do + setup do + @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{image/.*} + end + + should_accept_dummy_class + end + + context "given a class with other validations but matching types" do + setup do + @dummy_class.validates_presence_of :title + @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{image/.*} + end + + should_accept_dummy_class + end + + context "given a class that matches and a matcher that only specifies 'allowing'" do + setup do + @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{image/.*} + @matcher = self.class.validate_attachment_content_type(:avatar). + allowing(%w(image/png image/jpeg)) + end + + should_accept_dummy_class + end + + context "given a class that does not match and a matcher that only specifies 'allowing'" do + setup do + @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{audio/.*} + @matcher = self.class.validate_attachment_content_type(:avatar). + allowing(%w(image/png image/jpeg)) + end + + should_reject_dummy_class + end + + context "given a class that matches and a matcher that only specifies 'rejecting'" do + setup do + @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{image/.*} + @matcher = self.class.validate_attachment_content_type(:avatar). + rejecting(%w(audio/mp3 application/octet-stream)) + end + + should_accept_dummy_class + end + + context "given a class that does not match and a matcher that only specifies 'rejecting'" do + setup do + @dummy_class.validates_attachment_content_type :avatar, :content_type => %r{audio/.*} + @matcher = self.class.validate_attachment_content_type(:avatar). + rejecting(%w(audio/mp3 application/octet-stream)) + end + + should_reject_dummy_class + end + + context "using an :if to control the validation" do + setup do + @dummy_class.class_eval do + validates_attachment_content_type :avatar, :content_type => %r{image/*} , :if => :go + attr_accessor :go + end + @matcher = self.class.validate_attachment_content_type(:avatar). + allowing(%w(image/png image/jpeg)). + rejecting(%w(audio/mp3 application/octet-stream)) + @dummy = @dummy_class.new + end + + should "run the validation if the control is true" do + @dummy.go = true + assert_accepts @matcher, @dummy + end + + should "not run the validation if the control is false" do + @dummy.go = false + assert_rejects @matcher, @dummy + end + end + end +end diff --git a/vendor/gems/paperclip/test/matchers/validate_attachment_presence_matcher_test.rb b/vendor/gems/paperclip/test/matchers/validate_attachment_presence_matcher_test.rb new file mode 100644 index 0000000..341e4f8 --- /dev/null +++ b/vendor/gems/paperclip/test/matchers/validate_attachment_presence_matcher_test.rb @@ -0,0 +1,47 @@ +require './test/helper' + +class ValidateAttachmentPresenceMatcherTest < Test::Unit::TestCase + context "validate_attachment_presence" do + setup do + reset_table("dummies") do |d| + d.string :avatar_file_name + end + @dummy_class = reset_class "Dummy" + @dummy_class.has_attached_file :avatar + @matcher = self.class.validate_attachment_presence(:avatar) + end + + context "given a class with no validation" do + should_reject_dummy_class + end + + context "given a class with a matching validation" do + setup do + @dummy_class.validates_attachment_presence :avatar + end + + should_accept_dummy_class + end + + context "using an :if to control the validation" do + setup do + @dummy_class.class_eval do + validates_attachment_presence :avatar, :if => :go + attr_accessor :go + end + @dummy = @dummy_class.new + @dummy.avatar = nil + end + + should "run the validation if the control is true" do + @dummy.go = true + assert_accepts @matcher, @dummy + end + + should "not run the validation if the control is false" do + @dummy.go = false + assert_rejects @matcher, @dummy + end + end + end +end diff --git a/vendor/gems/paperclip/test/matchers/validate_attachment_size_matcher_test.rb b/vendor/gems/paperclip/test/matchers/validate_attachment_size_matcher_test.rb new file mode 100644 index 0000000..636b766 --- /dev/null +++ b/vendor/gems/paperclip/test/matchers/validate_attachment_size_matcher_test.rb @@ -0,0 +1,86 @@ +require './test/helper' + +class ValidateAttachmentSizeMatcherTest < Test::Unit::TestCase + context "validate_attachment_size" do + setup do + reset_table("dummies") do |d| + d.string :avatar_file_name + d.integer :avatar_file_size + end + @dummy_class = reset_class "Dummy" + @dummy_class.has_attached_file :avatar + end + + context "of limited size" do + setup{ @matcher = self.class.validate_attachment_size(:avatar).in(256..1024) } + + context "given a class with no validation" do + should_reject_dummy_class + end + + context "given a class with a validation that's too high" do + setup { @dummy_class.validates_attachment_size :avatar, :in => 256..2048 } + should_reject_dummy_class + end + + context "given a class with a validation that's too low" do + setup { @dummy_class.validates_attachment_size :avatar, :in => 0..1024 } + should_reject_dummy_class + end + + context "given a class with a validation that matches" do + setup { @dummy_class.validates_attachment_size :avatar, :in => 256..1024 } + should_accept_dummy_class + end + end + + context "allowing anything" do + setup{ @matcher = self.class.validate_attachment_size(:avatar) } + + context "given a class with an upper limit" do + setup { @dummy_class.validates_attachment_size :avatar, :less_than => 1 } + should_accept_dummy_class + end + + context "given a class with a lower limit" do + setup { @dummy_class.validates_attachment_size :avatar, :greater_than => 1 } + should_accept_dummy_class + end + end + + context "using an :if to control the validation" do + setup do + @dummy_class.class_eval do + validates_attachment_size :avatar, :greater_than => 1024, :if => :go + attr_accessor :go + end + @dummy = @dummy_class.new + @matcher = self.class.validate_attachment_size(:avatar).greater_than(1024) + end + + should "run the validation if the control is true" do + @dummy.go = true + assert_accepts @matcher, @dummy + end + + should "not run the validation if the control is false" do + @dummy.go = false + assert_rejects @matcher, @dummy + end + end + + context "post processing" do + setup do + @dummy_class.validates_attachment_size :avatar, :greater_than => 1024 + + @dummy = @dummy_class.new + @matcher = self.class.validate_attachment_size(:avatar).greater_than(1024) + end + + should "be skipped" do + @dummy.avatar.expects(:post_process).never + assert_accepts @matcher, @dummy + end + end + end +end diff --git a/vendor/gems/paperclip/test/paperclip_missing_attachment_styles_test.rb b/vendor/gems/paperclip/test/paperclip_missing_attachment_styles_test.rb new file mode 100644 index 0000000..bc168ac --- /dev/null +++ b/vendor/gems/paperclip/test/paperclip_missing_attachment_styles_test.rb @@ -0,0 +1,94 @@ +require './test/helper' + +class PaperclipMissingAttachmentStylesTest < Test::Unit::TestCase + + context "Paperclip" do + setup do + Paperclip.classes_with_attachments = Set.new + end + + teardown do + File.unlink(Paperclip.registered_attachments_styles_path) rescue nil + end + + should "be able to keep list of models using it" do + assert_kind_of Set, Paperclip.classes_with_attachments + assert Paperclip.classes_with_attachments.empty?, 'list should be empty' + rebuild_model + assert_equal ['Dummy'].to_set, Paperclip.classes_with_attachments + end + + should "enable to get and set path to registered styles file" do + assert_equal ROOT.join('tmp/public/system/paperclip_attachments.yml').to_s, Paperclip.registered_attachments_styles_path + Paperclip.registered_attachments_styles_path = '/tmp/config/paperclip_attachments.yml' + assert_equal '/tmp/config/paperclip_attachments.yml', Paperclip.registered_attachments_styles_path + Paperclip.registered_attachments_styles_path = nil + assert_equal ROOT.join('tmp/public/system/paperclip_attachments.yml').to_s, Paperclip.registered_attachments_styles_path + end + + should "be able to get current attachment styles" do + assert_equal Hash.new, Paperclip.send(:current_attachments_styles) + rebuild_model :styles => {:croppable => '600x600>', :big => '1000x1000>'} + expected_hash = { :Dummy => {:avatar => [:big, :croppable]}} + assert_equal expected_hash, Paperclip.send(:current_attachments_styles) + end + + should "be able to save current attachment styles for further comparison" do + rebuild_model :styles => {:croppable => '600x600>', :big => '1000x1000>'} + Paperclip.save_current_attachments_styles! + expected_hash = { :Dummy => {:avatar => [:big, :croppable]}} + assert_equal expected_hash, YAML.load_file(Paperclip.registered_attachments_styles_path) + end + + should "be able to read registered attachment styles from file" do + rebuild_model :styles => {:croppable => '600x600>', :big => '1000x1000>'} + Paperclip.save_current_attachments_styles! + expected_hash = { :Dummy => {:avatar => [:big, :croppable]}} + assert_equal expected_hash, Paperclip.send(:get_registered_attachments_styles) + end + + should "be able to calculate differences between registered styles and current styles" do + rebuild_model :styles => {:croppable => '600x600>', :big => '1000x1000>'} + Paperclip.save_current_attachments_styles! + rebuild_model :styles => {:thumb => 'x100', :export => 'x400>', :croppable => '600x600>', :big => '1000x1000>'} + expected_hash = { :Dummy => {:avatar => [:export, :thumb]} } + assert_equal expected_hash, Paperclip.missing_attachments_styles + + ActiveRecord::Base.connection.create_table :books, :force => true + class ::Book < ActiveRecord::Base + has_attached_file :cover, :styles => {:small => 'x100', :large => '1000x1000>'} + has_attached_file :sample, :styles => {:thumb => 'x100'} + end + + expected_hash = { + :Dummy => {:avatar => [:export, :thumb]}, + :Book => {:sample => [:thumb], :cover => [:large, :small]} + } + assert_equal expected_hash, Paperclip.missing_attachments_styles + Paperclip.save_current_attachments_styles! + assert_equal Hash.new, Paperclip.missing_attachments_styles + end + + should "be able to calculate differences when a new attachment is added to a model" do + rebuild_model :styles => {:croppable => '600x600>', :big => '1000x1000>'} + Paperclip.save_current_attachments_styles! + + class ::Dummy + has_attached_file :photo, :styles => {:small => 'x100', :large => '1000x1000>'} + end + + expected_hash = { + :Dummy => {:photo => [:large, :small]} + } + assert_equal expected_hash, Paperclip.missing_attachments_styles + Paperclip.save_current_attachments_styles! + assert_equal Hash.new, Paperclip.missing_attachments_styles + end + + # It's impossible to build styles hash without loading from database whole bunch of records + should "skip lambda-styles" do + rebuild_model :styles => lambda{ |attachment| attachment.instance.other == 'a' ? {:thumb => "50x50#"} : {:large => "400x400"} } + assert_equal Hash.new, Paperclip.send(:current_attachments_styles) + end + end +end diff --git a/vendor/gems/paperclip/test/paperclip_test.rb b/vendor/gems/paperclip/test/paperclip_test.rb new file mode 100644 index 0000000..a677f1f --- /dev/null +++ b/vendor/gems/paperclip/test/paperclip_test.rb @@ -0,0 +1,241 @@ +require './test/helper' + +class PaperclipTest < Test::Unit::TestCase + context "Calling Paperclip.run" do + setup do + Paperclip.options[:log_command] = false + Cocaine::CommandLine.expects(:new).with("convert", "stuff", {}).returns(stub(:run)) + @original_command_line_path = Cocaine::CommandLine.path + end + + teardown do + Paperclip.options[:log_command] = true + Cocaine::CommandLine.path = @original_command_line_path + end + + should "run the command with Cocaine" do + Paperclip.run("convert", "stuff") + end + + should "save Cocaine::CommandLine.path that set before" do + Cocaine::CommandLine.path = "/opt/my_app/bin" + Paperclip.run("convert", "stuff") + assert_equal [Cocaine::CommandLine.path].flatten.include?("/opt/my_app/bin"), true + end + + should "not duplicate Cocaine::CommandLine.path on multiple runs" do + Cocaine::CommandLine.expects(:new).with("convert", "more_stuff", {}).returns(stub(:run)) + Cocaine::CommandLine.path = nil + Paperclip.options[:command_path] = "/opt/my_app/bin" + Paperclip.run("convert", "stuff") + Paperclip.run("convert", "more_stuff") + assert_equal 1, [Cocaine::CommandLine.path].flatten.size + end + end + + context "Calling Paperclip.log without options[:logger] set" do + setup do + Paperclip.logger = nil + Paperclip.options[:logger] = nil + end + + teardown do + Paperclip.options[:logger] = ActiveRecord::Base.logger + Paperclip.logger = ActiveRecord::Base.logger + end + + should "not raise an error when log is called" do + silence_stream(STDOUT) do + Paperclip.log('something') + end + end + end + context "Calling Paperclip.run with a logger" do + should "pass the defined logger if :log_command is set" do + Paperclip.options[:log_command] = true + Cocaine::CommandLine.expects(:new).with("convert", "stuff", :logger => Paperclip.logger).returns(stub(:run)) + Paperclip.run("convert", "stuff") + end + end + + context "Paperclip.each_instance_with_attachment" do + setup do + @file = File.new(fixture_file("5k.png"), 'rb') + d1 = Dummy.create(:avatar => @file) + d2 = Dummy.create + d3 = Dummy.create(:avatar => @file) + @expected = [d1, d3] + end + + teardown { @file.close } + + should "yield every instance of a model that has an attachment" do + actual = [] + Paperclip.each_instance_with_attachment("Dummy", "avatar") do |instance| + actual << instance + end + assert_same_elements @expected, actual + end + end + + should "raise when sent #processor and the name of a class that doesn't exist" do + assert_raises(NameError){ Paperclip.processor(:boogey_man) } + end + + should "return a class when sent #processor and the name of a class under Paperclip" do + assert_equal ::Paperclip::Thumbnail, Paperclip.processor(:thumbnail) + end + + should "get a class from a namespaced class name" do + class ::One; class Two; end; end + assert_equal ::One::Two, Paperclip.class_for("One::Two") + end + + should "raise when class doesn't exist in specified namespace" do + class ::Three; end + class ::Four; end + assert_raise NameError do + Paperclip.class_for("Three::Four") + end + end + + context "Attachments with clashing URLs should raise error" do + setup do + class Dummy2 < ActiveRecord::Base + include Paperclip::Glue + end + end + + should "generate warning if attachment is redefined with the same url string" do + expected_log_msg = "Duplicate URL for blah with /system/:id/:style/:filename. This will clash with attachment defined in Dummy class" + Paperclip.expects(:log).with(expected_log_msg) + Dummy.class_eval do + has_attached_file :blah, :url => '/system/:id/:style/:filename' + end + Dummy2.class_eval do + has_attached_file :blah, :url => '/system/:id/:style/:filename' + end + end + + should "not generate warning if attachment is redifined with the same url string but has :class in it" do + Paperclip.expects(:log).never + Dummy.class_eval do + has_attached_file :blah, :url => "/system/:class/:attachment/:id/:style/:filename" + end + Dummy2.class_eval do + has_attached_file :blah, :url => "/system/:class/:attachment/:id/:style/:filename" + end + end + end + + context "An ActiveRecord model with an 'avatar' attachment" do + setup do + rebuild_model :path => "tmp/:class/omg/:style.:extension" + @file = File.new(fixture_file("5k.png"), 'rb') + end + + teardown { @file.close } + + should "not error when trying to also create a 'blah' attachment" do + assert_nothing_raised do + Dummy.class_eval do + has_attached_file :blah + end + end + end + + context "that is attr_protected" do + setup do + Dummy.class_eval do + attr_protected :avatar + end + @dummy = Dummy.new + end + + should "not assign the avatar on mass-set" do + @dummy.attributes = { :other => "I'm set!", + :avatar => @file } + + assert_equal "I'm set!", @dummy.other + assert ! @dummy.avatar? + end + + should "still allow assigment on normal set" do + @dummy.other = "I'm set!" + @dummy.avatar = @file + + assert_equal "I'm set!", @dummy.other + assert @dummy.avatar? + end + end + + context "with a subclass" do + setup do + class ::SubDummy < Dummy; end + end + + should "be able to use the attachment from the subclass" do + assert_nothing_raised do + @subdummy = SubDummy.create(:avatar => @file) + end + end + + should "be able to see the attachment definition from the subclass's class" do + assert_equal "tmp/:class/omg/:style.:extension", + SubDummy.attachment_definitions[:avatar][:path] + end + + teardown do + SubDummy.delete_all + Object.send(:remove_const, "SubDummy") rescue nil + end + end + + should "have an #avatar method" do + assert Dummy.new.respond_to?(:avatar) + end + + should "have an #avatar= method" do + assert Dummy.new.respond_to?(:avatar=) + end + + context "that is valid" do + setup do + @dummy = Dummy.new + @dummy.avatar = @file + end + + should "be valid" do + assert @dummy.valid? + end + end + + should "not have Attachment in the ActiveRecord::Base namespace" do + assert_raises(NameError) do + ActiveRecord::Base::Attachment + end + end + end + + context "configuring a custom processor" do + setup do + @freedom_processor = Class.new do + def make(file, options = {}, attachment = nil) + file + end + end.new + + Paperclip.configure do |config| + config.register_processor(:freedom, @freedom_processor) + end + end + + should "be able to find the custom processor" do + assert_equal @freedom_processor, Paperclip.processor(:freedom) + end + + teardown do + Paperclip.clear_processors! + end + end +end diff --git a/vendor/gems/paperclip/test/processor_test.rb b/vendor/gems/paperclip/test/processor_test.rb new file mode 100644 index 0000000..50cb1dd --- /dev/null +++ b/vendor/gems/paperclip/test/processor_test.rb @@ -0,0 +1,26 @@ +require './test/helper' + +class ProcessorTest < Test::Unit::TestCase + should "instantiate and call #make when sent #make to the class" do + processor = mock + processor.expects(:make).with() + Paperclip::Processor.expects(:new).with(:one, :two, :three).returns(processor) + Paperclip::Processor.make(:one, :two, :three) + end + + context "Calling #convert" do + should "run the convert command with Cocaine" do + Paperclip.options[:log_command] = false + Cocaine::CommandLine.expects(:new).with("convert", "stuff", {}).returns(stub(:run)) + Paperclip::Processor.new('filename').convert("stuff") + end + end + + context "Calling #identify" do + should "run the identify command with Cocaine" do + Paperclip.options[:log_command] = false + Cocaine::CommandLine.expects(:new).with("identify", "stuff", {}).returns(stub(:run)) + Paperclip::Processor.new('filename').identify("stuff") + end + end +end diff --git a/vendor/gems/paperclip/test/schema_test.rb b/vendor/gems/paperclip/test/schema_test.rb new file mode 100644 index 0000000..d18856b --- /dev/null +++ b/vendor/gems/paperclip/test/schema_test.rb @@ -0,0 +1,200 @@ +require './test/helper' +require 'paperclip/schema' +require 'active_support/testing/deprecation' + +class SchemaTest < Test::Unit::TestCase + include ActiveSupport::Testing::Deprecation + + def setup + rebuild_class + end + + def teardown + Dummy.connection.drop_table :dummies rescue nil + end + + context "within table definition" do + context "using #has_attached_file" do + should "create attachment columns" do + Dummy.connection.create_table :dummies, :force => true do |t| + ActiveSupport::Deprecation.silence do + t.has_attached_file :avatar + end + end + rebuild_class + + columns = Dummy.columns.map{ |column| [column.name, column.type] } + + assert_includes columns, ['avatar_file_name', :string] + assert_includes columns, ['avatar_content_type', :string] + assert_includes columns, ['avatar_file_size', :integer] + assert_includes columns, ['avatar_updated_at', :datetime] + end + + should "display deprecation warning" do + Dummy.connection.create_table :dummies, :force => true do |t| + assert_deprecated do + t.has_attached_file :avatar + end + end + end + end + + context "using #attachment" do + setup do + Dummy.connection.create_table :dummies, :force => true do |t| + t.attachment :avatar + end + rebuild_class + end + + should "create attachment columns" do + columns = Dummy.columns.map{ |column| [column.name, column.type] } + + assert_includes columns, ['avatar_file_name', :string] + assert_includes columns, ['avatar_content_type', :string] + assert_includes columns, ['avatar_file_size', :integer] + assert_includes columns, ['avatar_updated_at', :datetime] + end + end + end + + context "within schema statement" do + setup do + Dummy.connection.create_table :dummies, :force => true + end + + context "migrating up" do + context "with single attachment" do + setup do + Dummy.connection.add_attachment :dummies, :avatar + rebuild_class + end + + should "create attachment columns" do + columns = Dummy.columns.map{ |column| [column.name, column.type] } + + assert_includes columns, ['avatar_file_name', :string] + assert_includes columns, ['avatar_content_type', :string] + assert_includes columns, ['avatar_file_size', :integer] + assert_includes columns, ['avatar_updated_at', :datetime] + end + end + + context "with multiple attachments" do + setup do + Dummy.connection.add_attachment :dummies, :avatar, :photo + rebuild_class + end + + should "create attachment columns" do + columns = Dummy.columns.map{ |column| [column.name, column.type] } + + assert_includes columns, ['avatar_file_name', :string] + assert_includes columns, ['avatar_content_type', :string] + assert_includes columns, ['avatar_file_size', :integer] + assert_includes columns, ['avatar_updated_at', :datetime] + assert_includes columns, ['photo_file_name', :string] + assert_includes columns, ['photo_content_type', :string] + assert_includes columns, ['photo_file_size', :integer] + assert_includes columns, ['photo_updated_at', :datetime] + end + end + + context "with no attachment" do + should "raise an error" do + assert_raise ArgumentError do + Dummy.connection.add_attachment :dummies + rebuild_class + end + end + end + end + + context "migrating down" do + setup do + Dummy.connection.change_table :dummies do |t| + t.column :avatar_file_name, :string + t.column :avatar_content_type, :string + t.column :avatar_file_size, :integer + t.column :avatar_updated_at, :datetime + end + end + + context "using #drop_attached_file" do + should "remove the attachment columns" do + ActiveSupport::Deprecation.silence do + Dummy.connection.drop_attached_file :dummies, :avatar + end + rebuild_class + + columns = Dummy.columns.map{ |column| [column.name, column.type] } + + assert_not_includes columns, ['avatar_file_name', :string] + assert_not_includes columns, ['avatar_content_type', :string] + assert_not_includes columns, ['avatar_file_size', :integer] + assert_not_includes columns, ['avatar_updated_at', :datetime] + end + + should "display a deprecation warning" do + assert_deprecated do + Dummy.connection.drop_attached_file :dummies, :avatar + end + end + end + + context "using #remove_attachment" do + context "with single attachment" do + setup do + Dummy.connection.remove_attachment :dummies, :avatar + rebuild_class + end + + should "remove the attachment columns" do + columns = Dummy.columns.map{ |column| [column.name, column.type] } + + assert_not_includes columns, ['avatar_file_name', :string] + assert_not_includes columns, ['avatar_content_type', :string] + assert_not_includes columns, ['avatar_file_size', :integer] + assert_not_includes columns, ['avatar_updated_at', :datetime] + end + end + + context "with multiple attachments" do + setup do + Dummy.connection.change_table :dummies do |t| + t.column :photo_file_name, :string + t.column :photo_content_type, :string + t.column :photo_file_size, :integer + t.column :photo_updated_at, :datetime + end + + Dummy.connection.remove_attachment :dummies, :avatar, :photo + rebuild_class + end + + should "remove the attachment columns" do + columns = Dummy.columns.map{ |column| [column.name, column.type] } + + assert_not_includes columns, ['avatar_file_name', :string] + assert_not_includes columns, ['avatar_content_type', :string] + assert_not_includes columns, ['avatar_file_size', :integer] + assert_not_includes columns, ['avatar_updated_at', :datetime] + assert_not_includes columns, ['photo_file_name', :string] + assert_not_includes columns, ['photo_content_type', :string] + assert_not_includes columns, ['photo_file_size', :integer] + assert_not_includes columns, ['photo_updated_at', :datetime] + end + end + + context "with no attachment" do + should "raise an error" do + assert_raise ArgumentError do + Dummy.connection.remove_attachment :dummies + end + end + end + end + end + end +end diff --git a/vendor/gems/paperclip/test/storage/filesystem_test.rb b/vendor/gems/paperclip/test/storage/filesystem_test.rb new file mode 100644 index 0000000..0a51313 --- /dev/null +++ b/vendor/gems/paperclip/test/storage/filesystem_test.rb @@ -0,0 +1,71 @@ +require './test/helper' + +class FileSystemTest < Test::Unit::TestCase + context "Filesystem" do + context "normal file" do + setup do + rebuild_model :styles => { :thumbnail => "25x25#" } + @dummy = Dummy.create! + + @file = File.open(fixture_file('5k.png')) + @dummy.avatar = @file + end + + teardown { @file.close } + + should "allow file assignment" do + assert @dummy.save + end + + should "store the original" do + @dummy.save + assert_file_exists(@dummy.avatar.path) + end + + should "store the thumbnail" do + @dummy.save + assert_file_exists(@dummy.avatar.path(:thumbnail)) + end + + should "be rewinded after flush_writes" do + @dummy.avatar.instance_eval "def after_flush_writes; end" + + files = @dummy.avatar.queued_for_write.values + @dummy.save + assert files.none?(&:eof?), "Expect all the files to be rewinded." + end + + should "be removed after after_flush_writes" do + paths = @dummy.avatar.queued_for_write.values.map(&:path) + @dummy.save + assert paths.none?{ |path| File.exists?(path) }, + "Expect all the files to be deleted." + end + end + + context "with file that has space in file name" do + setup do + rebuild_model :styles => { :thumbnail => "25x25#" } + @dummy = Dummy.create! + + @file = File.open(fixture_file('spaced file.png')) + @dummy.avatar = @file + @dummy.save + end + + teardown { @file.close } + + should "store the file" do + assert_file_exists(@dummy.avatar.path) + end + + should "return a replaced version for path" do + assert_match /.+\/spaced_file\.png/, @dummy.avatar.path + end + + should "return a replaced version for url" do + assert_match /.+\/spaced_file\.png/, @dummy.avatar.url + end + end + end +end diff --git a/vendor/gems/paperclip/test/storage/fog_test.rb b/vendor/gems/paperclip/test/storage/fog_test.rb new file mode 100644 index 0000000..6f30d71 --- /dev/null +++ b/vendor/gems/paperclip/test/storage/fog_test.rb @@ -0,0 +1,353 @@ +require './test/helper' +require 'fog' + +Fog.mock! + +class FogTest < Test::Unit::TestCase + context "" do + context "with credentials provided in a path string" do + setup do + rebuild_model :styles => { :medium => "300x300>", :thumb => "100x100>" }, + :storage => :fog, + :url => '/:attachment/:filename', + :fog_directory => "paperclip", + :fog_credentials => fixture_file('fog.yml') + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + should "have the proper information loading credentials from a file" do + assert_equal @dummy.avatar.fog_credentials[:provider], 'AWS' + end + end + + context "with credentials provided in a File object" do + setup do + rebuild_model :styles => { :medium => "300x300>", :thumb => "100x100>" }, + :storage => :fog, + :url => '/:attachment/:filename', + :fog_directory => "paperclip", + :fog_credentials => File.open(fixture_file('fog.yml')) + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + should "have the proper information loading credentials from a file" do + assert_equal @dummy.avatar.fog_credentials[:provider], 'AWS' + end + end + + context "with default values for path and url" do + setup do + rebuild_model :styles => { :medium => "300x300>", :thumb => "100x100>" }, + :storage => :fog, + :url => '/:attachment/:filename', + :fog_directory => "paperclip", + :fog_credentials => { + :provider => 'AWS', + :aws_access_key_id => 'AWS_ID', + :aws_secret_access_key => 'AWS_SECRET' + } + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + should "be able to interpolate the path without blowing up" do + assert_equal File.expand_path(File.join(File.dirname(__FILE__), "../../tmp/public/avatars/5k.png")), + @dummy.avatar.path + end + end + + context "with no path or url given and using defaults" do + setup do + rebuild_model :styles => { :medium => "300x300>", :thumb => "100x100>" }, + :storage => :fog, + :fog_directory => "paperclip", + :fog_credentials => { + :provider => 'AWS', + :aws_access_key_id => 'AWS_ID', + :aws_secret_access_key => 'AWS_SECRET' + } + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.id = 1 + @dummy.avatar = @file + end + + teardown { @file.close } + + should "have correct path and url from interpolated defaults" do + assert_equal "dummies/avatars/000/000/001/original/5k.png", @dummy.avatar.path + end + end + + setup do + @fog_directory = 'papercliptests' + + @credentials = { + :provider => 'AWS', + :aws_access_key_id => 'ID', + :aws_secret_access_key => 'SECRET' + } + + @connection = Fog::Storage.new(@credentials) + @connection.directories.create( + :key => @fog_directory + ) + + @options = { + :fog_directory => @fog_directory, + :fog_credentials => @credentials, + :fog_host => nil, + :fog_file => {:cache_control => 1234}, + :path => ":attachment/:basename.:extension", + :storage => :fog + } + + rebuild_model(@options) + end + + should "be extended by the Fog module" do + assert Dummy.new.avatar.is_a?(Paperclip::Storage::Fog) + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown do + @file.close + directory = @connection.directories.new(:key => @fog_directory) + directory.files.each {|file| file.destroy} + directory.destroy + end + + should "be rewinded after flush_writes" do + @dummy.avatar.instance_eval "def after_flush_writes; end" + + files = @dummy.avatar.queued_for_write.values + @dummy.save + assert files.none?(&:eof?), "Expect all the files to be rewinded." + end + + should "be removed after after_flush_writes" do + paths = @dummy.avatar.queued_for_write.values.map(&:path) + @dummy.save + assert paths.none?{ |path| File.exists?(path) }, + "Expect all the files to be deleted." + end + + should "pass the content type to the Fog::Storage::AWS::Files instance" do + Fog::Storage::AWS::Files.any_instance.expects(:create).with do |hash| + hash[:content_type] + end + @dummy.save + end + + context "without a bucket" do + setup do + @connection.directories.get(@fog_directory).destroy + end + + should "create the bucket" do + assert @dummy.save + assert @connection.directories.get(@fog_directory) + end + end + + context "with a bucket" do + should "succeed" do + assert @dummy.save + end + end + + context "without a fog_host" do + setup do + rebuild_model(@options.merge(:fog_host => nil)) + @dummy = Dummy.new + @dummy.avatar = StringIO.new('.') + @dummy.save + end + + should "provide a public url" do + assert !@dummy.avatar.url.nil? + end + end + + context "with a fog_host" do + setup do + rebuild_model(@options.merge(:fog_host => 'http://example.com')) + @dummy = Dummy.new + @dummy.avatar = StringIO.new('.') + @dummy.save + end + + should "provide a public url" do + assert @dummy.avatar.url =~ /^http:\/\/example\.com\/avatars\/stringio\.txt\?\d*$/ + end + end + + context "with a fog_host that includes a wildcard placeholder" do + setup do + rebuild_model( + :fog_directory => @fog_directory, + :fog_credentials => @credentials, + :fog_host => 'http://img%d.example.com', + :path => ":attachment/:basename.:extension", + :storage => :fog + ) + @dummy = Dummy.new + @dummy.avatar = StringIO.new('.') + @dummy.save + end + + should "provide a public url" do + assert @dummy.avatar.url =~ /^http:\/\/img[0123]\.example\.com\/avatars\/stringio\.txt\?\d*$/ + end + end + + context "with fog_public set to false" do + setup do + rebuild_model(@options.merge(:fog_public => false)) + @dummy = Dummy.new + @dummy.avatar = StringIO.new('.') + @dummy.save + end + + should 'set the @fog_public instance variable to false' do + assert_equal false, @dummy.avatar.instance_variable_get('@options')[:fog_public] + assert_equal false, @dummy.avatar.fog_public + end + end + + context "with a valid bucket name for a subdomain" do + should "provide an url in subdomain style" do + assert_match @dummy.avatar.url, /^https:\/\/papercliptests.s3.amazonaws.com\/avatars\/5k.png/ + end + + should "provide an url that expires in subdomain style" do + assert_match /^http:\/\/papercliptests.s3.amazonaws.com\/avatars\/5k.png\?AWSAccessKeyId=.+$/, @dummy.avatar.expiring_url + end + end + + context "with an invalid bucket name for a subdomain" do + setup do + rebuild_model(@options.merge(:fog_directory => "this_is_invalid")) + @dummy = Dummy.new + @dummy.avatar = @file + @dummy.save + end + + should "not match the bucket-subdomain restrictions" do + invalid_subdomains = %w(this_is_invalid in iamareallylongbucketnameiamareallylongbucketnameiamareallylongbu invalid- inval..id inval-.id inval.-id -invalid 192.168.10.2) + invalid_subdomains.each do |name| + assert_no_match Paperclip::Storage::Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX, name + end + end + + should "provide an url in folder style" do + assert_match /^https:\/\/s3.amazonaws.com\/this_is_invalid\/avatars\/5k.png\?\d*$/, @dummy.avatar.url + end + + should "provide a url that expires in folder style" do + assert_match /^http:\/\/s3.amazonaws.com\/this_is_invalid\/avatars\/5k.png\?AWSAccessKeyId=.+$/, @dummy.avatar.expiring_url + end + + end + + context "with a proc for a bucket name evaluating a model method" do + setup do + @dynamic_fog_directory = 'dynamicpaperclip' + rebuild_model(@options.merge(:fog_directory => lambda { |attachment| attachment.instance.bucket_name })) + @dummy = Dummy.new + @dummy.stubs(:bucket_name).returns(@dynamic_fog_directory) + @dummy.avatar = @file + @dummy.save + end + + should "have created the bucket" do + assert @connection.directories.get(@dynamic_fog_directory).inspect + end + + end + + context "with a proc for the fog_host evaluating a model method" do + setup do + rebuild_model(@options.merge(:fog_host => lambda { |attachment| attachment.instance.fog_host })) + @dummy = Dummy.new + @dummy.stubs(:fog_host).returns('http://dynamicfoghost.com') + @dummy.avatar = @file + @dummy.save + end + + should "provide a public url" do + assert_match /http:\/\/dynamicfoghost\.com/, @dummy.avatar.url + end + + end + + context "with a custom fog_host" do + setup do + rebuild_model(@options.merge(:fog_host => "http://dynamicfoghost.com")) + @dummy = Dummy.new + @dummy.avatar = @file + @dummy.save + end + + should "provide a public url" do + assert_match /http:\/\/dynamicfoghost\.com/, @dummy.avatar.url + end + + should "provide an expiring url" do + assert_match /http:\/\/dynamicfoghost\.com/, @dummy.avatar.expiring_url + end + + context "with an invalid bucket name for a subdomain" do + setup do + rebuild_model(@options.merge({:fog_directory => "this_is_invalid", :fog_host => "http://dynamicfoghost.com"})) + @dummy = Dummy.new + @dummy.avatar = @file + @dummy.save + end + + should "provide an expiring url" do + assert_match /http:\/\/dynamicfoghost\.com/, @dummy.avatar.expiring_url + end + end + + end + + context "with a proc for the fog_credentials evaluating a model method" do + setup do + @dynamic_fog_credentials = { + :provider => 'AWS', + :aws_access_key_id => 'DYNAMIC_ID', + :aws_secret_access_key => 'DYNAMIC_SECRET' + } + rebuild_model(@options.merge(:fog_credentials => lambda { |attachment| attachment.instance.fog_credentials })) + @dummy = Dummy.new + @dummy.stubs(:fog_credentials).returns(@dynamic_fog_credentials) + @dummy.avatar = @file + @dummy.save + end + + should "provide a public url" do + assert_equal @dummy.avatar.fog_credentials, @dynamic_fog_credentials + end + end + end + + end +end diff --git a/vendor/gems/paperclip/test/storage/s3_live_test.rb b/vendor/gems/paperclip/test/storage/s3_live_test.rb new file mode 100644 index 0000000..388cb94 --- /dev/null +++ b/vendor/gems/paperclip/test/storage/s3_live_test.rb @@ -0,0 +1,179 @@ +require './test/helper' +require 'aws' + +unless ENV["S3_BUCKET"].blank? + class S3LiveTest < Test::Unit::TestCase + context "when assigning an S3 attachment directly to another model" do + setup do + @s3_credentials = File.new(fixture_file("s3.yml")) + rebuild_model :styles => { :thumb => "100x100", :square => "32x32#" }, + :storage => :s3, + :bucket => ENV["S3_BUCKET"], + :path => ":class/:attachment/:id/:style.:extension", + :s3_credentials => @s3_credentials + + @file = File.new(fixture_file("5k.png")) + end + + should "not raise any error" do + @attachment = Dummy.new.avatar + @attachment.assign(@file) + @attachment.save + + @attachment2 = Dummy.new.avatar + @attachment2.assign(@file) + @attachment2.save + end + + should "allow assignment from another S3 object" do + @attachment = Dummy.new.avatar + @attachment.assign(@file) + @attachment.save + + @attachment2 = Dummy.new.avatar + @attachment2.assign(@attachment) + @attachment2.save + end + + teardown { [@s3_credentials, @file].each(&:close) } + end + + context "Generating an expiring url on a nonexistant attachment" do + setup do + @s3_credentials = File.new(fixture_file("s3.yml")) + rebuild_model :styles => { :thumb => "100x100", :square => "32x32#" }, + :storage => :s3, + :bucket => ENV["S3_BUCKET"], + :path => ":class/:attachment/:id/:style.:extension", + :s3_credentials => @s3_credentials + + @dummy = Dummy.new + end + + should "return nil" do + assert_nil @dummy.avatar.expiring_url + end + end + + context "Using S3 for real, an attachment with S3 storage" do + setup do + @s3_credentials = File.new(fixture_file("s3.yml")) + rebuild_model :styles => { :thumb => "100x100", :square => "32x32#" }, + :storage => :s3, + :bucket => ENV["S3_BUCKET"], + :path => ":class/:attachment/:id/:style.:extension", + :s3_credentials => @s3_credentials + + Dummy.delete_all + @dummy = Dummy.new + end + + teardown { @s3_credentials.close } + + should "be extended by the S3 module" do + assert Dummy.new.avatar.is_a?(Paperclip::Storage::S3) + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy.avatar = @file + end + + teardown do + @file.close + @dummy.destroy + end + + context "and saved" do + setup do + @dummy.save + end + + should "be on S3" do + assert true + end + end + end + end + + context "An attachment that uses S3 for storage and has spaces in file name" do + setup do + @s3_credentials = File.new(fixture_file("s3.yml")) + rebuild_model :styles => { :thumb => "100x100", :square => "32x32#" }, + :storage => :s3, + :bucket => ENV["S3_BUCKET"], + :s3_credentials => @s3_credentials + + Dummy.delete_all + @file = File.new(fixture_file('spaced file.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + @dummy.save + end + + teardown { @s3_credentials.close } + + should "return a replaced version for path" do + assert_match /.+\/spaced_file\.png/, @dummy.avatar.path + end + + should "return a replaced version for url" do + assert_match /.+\/spaced_file\.png/, @dummy.avatar.url + end + + should "be accessible" do + assert_success_response @dummy.avatar.url + end + + should "be reprocessable" do + assert @dummy.avatar.reprocess! + end + + should "be destroyable" do + url = @dummy.avatar.url + @dummy.destroy + assert_not_found_response url + end + end + + context "An attachment that uses S3 for storage and uses AES256 encryption" do + setup do + @s3_credentials = File.new(fixture_file("s3.yml")) + rebuild_model :styles => { :thumb => "100x100", :square => "32x32#" }, + :storage => :s3, + :bucket => ENV["S3_BUCKET"], + :path => ":class/:attachment/:id/:style.:extension", + :s3_credentials => @s3_credentials, + :s3_server_side_encryption => :aes256 + + Dummy.delete_all + @dummy = Dummy.new + end + + teardown { @s3_credentials.close } + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy.avatar = @file + end + + teardown do + @file.close + @dummy.destroy + end + + context "and saved" do + setup do + @dummy.save + end + + should "be encrypted on S3" do + assert @dummy.avatar.s3_object.server_side_encryption == :aes256 + end + end + end + end + end +end diff --git a/vendor/gems/paperclip/test/storage/s3_test.rb b/vendor/gems/paperclip/test/storage/s3_test.rb new file mode 100644 index 0000000..0a17d77 --- /dev/null +++ b/vendor/gems/paperclip/test/storage/s3_test.rb @@ -0,0 +1,1196 @@ +require './test/helper' +require 'aws' + +class S3Test < Test::Unit::TestCase + def rails_env(env) + silence_warnings do + Object.const_set(:Rails, stub('Rails', :env => env)) + end + end + + def setup + AWS.config(:access_key_id => "TESTKEY", :secret_access_key => "TESTSECRET", :stub_requests => true) + end + + def teardown + AWS.config(:access_key_id => nil, :secret_access_key => nil, :stub_requests => nil) + end + + context "Parsing S3 credentials" do + setup do + @proxy_settings = {:host => "127.0.0.1", :port => 8888, :user => "foo", :password => "bar"} + rebuild_model :storage => :s3, + :bucket => "testing", + :http_proxy => @proxy_settings, + :s3_credentials => {:not => :important} + + @dummy = Dummy.new + @avatar = @dummy.avatar + end + + should "get the correct credentials when RAILS_ENV is production" do + rails_env("production") + assert_equal({:key => "12345"}, + @avatar.parse_credentials('production' => {:key => '12345'}, + :development => {:key => "54321"})) + end + + should "get the correct credentials when RAILS_ENV is development" do + rails_env("development") + assert_equal({:key => "54321"}, + @avatar.parse_credentials('production' => {:key => '12345'}, + :development => {:key => "54321"})) + end + + should "return the argument if the key does not exist" do + rails_env("not really an env") + assert_equal({:test => "12345"}, @avatar.parse_credentials(:test => "12345")) + end + + should "support HTTP proxy settings" do + rails_env("development") + assert_equal(true, @avatar.using_http_proxy?) + assert_equal(@proxy_settings[:host], @avatar.http_proxy_host) + assert_equal(@proxy_settings[:port], @avatar.http_proxy_port) + assert_equal(@proxy_settings[:user], @avatar.http_proxy_user) + assert_equal(@proxy_settings[:password], @avatar.http_proxy_password) + end + + end + + context ":bucket option via :s3_credentials" do + + setup do + rebuild_model :storage => :s3, :s3_credentials => {:bucket => 'testing'} + @dummy = Dummy.new + end + + should "populate #bucket_name" do + assert_equal @dummy.avatar.bucket_name, 'testing' + end + + end + + context ":bucket option" do + + setup do + rebuild_model :storage => :s3, :bucket => "testing", :s3_credentials => {} + @dummy = Dummy.new + end + + should "populate #bucket_name" do + assert_equal @dummy.avatar.bucket_name, 'testing' + end + + end + + context "missing :bucket option" do + + setup do + rebuild_model :storage => :s3, + :http_proxy => @proxy_settings, + :s3_credentials => {:not => :important} + + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + + end + + should "raise an argument error" do + exception = assert_raise(ArgumentError) { @dummy.save } + assert_match /missing required :bucket option/, exception.message + end + + end + + context "" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => {}, + :bucket => "bucket", + :path => ":attachment/:basename.:extension", + :url => ":s3_path_url" + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + end + + should "return a url based on an S3 path" do + assert_match %r{^http://s3.amazonaws.com/bucket/avatars/stringio.txt}, @dummy.avatar.url + end + + should "use the correct bucket" do + assert_equal "bucket", @dummy.avatar.s3_bucket.name + end + + should "use the correct key" do + assert_equal "avatars/stringio.txt", @dummy.avatar.s3_object.key + end + end + + context "s3_protocol" do + ["http", :http, ""].each do |protocol| + context "as #{protocol.inspect}" do + setup do + rebuild_model :storage => :s3, :s3_protocol => protocol + + @dummy = Dummy.new + end + + should "return the s3_protocol in string" do + assert_equal protocol.to_s, @dummy.avatar.s3_protocol + end + end + end + end + + context ":s3_protocol => 'https'" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => {}, + :s3_protocol => 'https', + :bucket => "bucket", + :path => ":attachment/:basename.:extension" + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + end + + should "return a url based on an S3 path" do + assert_match %r{^https://s3.amazonaws.com/bucket/avatars/stringio.txt}, @dummy.avatar.url + end + end + + context ":s3_protocol => :https" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => {}, + :s3_protocol => :https, + :bucket => "bucket", + :path => ":attachment/:basename.:extension" + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + end + + should "return a url based on an S3 path" do + assert_match %r{^https://s3.amazonaws.com/bucket/avatars/stringio.txt}, @dummy.avatar.url + end + end + + context ":s3_protocol => ''" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => {}, + :s3_protocol => '', + :bucket => "bucket", + :path => ":attachment/:basename.:extension" + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + end + + should "return a url based on an S3 path" do + assert_match %r{^//s3.amazonaws.com/bucket/avatars/stringio.txt}, @dummy.avatar.url + end + end + + context "An attachment that uses S3 for storage and has the style in the path" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :styles => { + :thumb => "80x80>" + }, + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + } + + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + @avatar = @dummy.avatar + end + + should "use an S3 object based on the correct path for the default style" do + assert_equal("avatars/original/stringio.txt", @dummy.avatar.s3_object.key) + end + + should "use an S3 object based on the correct path for the custom style" do + assert_equal("avatars/thumb/stringio.txt", @dummy.avatar.s3_object(:thumb).key) + end + end + + context "s3_host_name" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => {}, + :bucket => "bucket", + :path => ":attachment/:basename.:extension", + :s3_host_name => "s3-ap-northeast-1.amazonaws.com" + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + end + + should "return a url based on an :s3_host_name path" do + assert_match %r{^http://s3-ap-northeast-1.amazonaws.com/bucket/avatars/stringio.txt}, @dummy.avatar.url + end + + should "use the S3 bucket with the correct host name" do + assert_equal "s3-ap-northeast-1.amazonaws.com", @dummy.avatar.s3_bucket.config.s3_endpoint + end + end + + context "An attachment that uses S3 for storage and has styles that return different file types" do + setup do + rebuild_model :styles => { :large => ['500x500#', :jpg] }, + :storage => :s3, + :bucket => "bucket", + :path => ":attachment/:basename.:extension", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + } + + File.open(fixture_file('5k.png'), 'rb') do |file| + @dummy = Dummy.new + @dummy.avatar = file + end + end + + should "return a url containing the correct original file mime type" do + assert_match /.+\/5k.png/, @dummy.avatar.url + end + + should 'use the correct key for the original file mime type' do + assert_match /.+\/5k.png/, @dummy.avatar.s3_object.key + end + + should "return a url containing the correct processed file mime type" do + assert_match /.+\/5k.jpg/, @dummy.avatar.url(:large) + end + + should "use the correct key for the processed file mime type" do + assert_match /.+\/5k.jpg/, @dummy.avatar.s3_object(:large).key + end + end + + context "An attachment that uses S3 for storage and has spaces in file name" do + setup do + rebuild_model :styles => { :large => ['500x500#', :jpg] }, + :storage => :s3, + :bucket => "bucket", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + } + + File.open(fixture_file('spaced file.png'), 'rb') do |file| + @dummy = Dummy.new + @dummy.avatar = file + end + end + + should "return a replaced version for path" do + assert_match /.+\/spaced_file\.png/, @dummy.avatar.path + end + + should "return a replaced version for url" do + assert_match /.+\/spaced_file\.png/, @dummy.avatar.url + end + end + + context "An attachment that uses S3 for storage and has a question mark in file name" do + setup do + rebuild_model :styles => { :large => ['500x500#', :jpg] }, + :storage => :s3, + :bucket => "bucket", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + } + + stringio = StringIO.new(".") + class << stringio + def original_filename + "question?mark.png" + end + end + file = Paperclip.io_adapters.for(stringio) + @dummy = Dummy.new + @dummy.avatar = file + @dummy.save + end + + should "return a replaced version for path" do + assert_match /.+\/question_mark\.png/, @dummy.avatar.path + end + + should "return a replaced version for url" do + assert_match /.+\/question_mark\.png/, @dummy.avatar.url + end + end + + context "" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => {}, + :bucket => "bucket", + :path => ":attachment/:basename.:extension", + :url => ":s3_domain_url" + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + end + + should "return a url based on an S3 subdomain" do + assert_match %r{^http://bucket.s3.amazonaws.com/avatars/stringio.txt}, @dummy.avatar.url + end + end + + context "" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => { + :production => { :bucket => "prod_bucket" }, + :development => { :bucket => "dev_bucket" } + }, + :s3_host_alias => "something.something.com", + :path => ":attachment/:basename.:extension", + :url => ":s3_alias_url" + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + end + + should "return a url based on the host_alias" do + assert_match %r{^http://something.something.com/avatars/stringio.txt}, @dummy.avatar.url + end + end + + context "generating a url with a proc as the host alias" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => { :bucket => "prod_bucket" }, + :s3_host_alias => Proc.new{|atch| "cdn#{atch.instance.counter % 4}.example.com"}, + :path => ":attachment/:basename.:extension", + :url => ":s3_alias_url" + Dummy.class_eval do + def counter + @counter ||= 0 + @counter += 1 + @counter + end + end + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + end + + should "return a url based on the host_alias" do + assert_match %r{^http://cdn1.example.com/avatars/stringio.txt}, @dummy.avatar.url + assert_match %r{^http://cdn2.example.com/avatars/stringio.txt}, @dummy.avatar.url + end + + should "still return the bucket name" do + assert_equal "prod_bucket", @dummy.avatar.bucket_name + end + + end + + context "" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => {}, + :bucket => "bucket", + :path => ":attachment/:basename.:extension", + :url => ":asset_host" + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + end + + should "return a relative URL for Rails to calculate assets host" do + assert_match %r{^avatars/stringio\.txt}, @dummy.avatar.url + end + + end + + context "Generating a secure url with an expiration" do + setup do + @build_model_with_options = lambda {|options| + base_options = { + :storage => :s3, + :s3_credentials => { + :production => { :bucket => "prod_bucket" }, + :development => { :bucket => "dev_bucket" } + }, + :s3_host_alias => "something.something.com", + :s3_permissions => "private", + :path => ":attachment/:basename.:extension", + :url => ":s3_alias_url" + } + + rebuild_model base_options.merge(options) + } + end + + should "use default options" do + @build_model_with_options[{}] + + rails_env("production") + + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:url_for).with(:read, :expires => 3600, :secure => true) + + @dummy.avatar.expiring_url + end + + should "allow overriding s3_url_options" do + @build_model_with_options[:s3_url_options => { :response_content_disposition => "inline" }] + + rails_env("production") + + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:url_for).with(:read, :expires => 3600, :secure => true, :response_content_disposition => "inline") + + @dummy.avatar.expiring_url + end + + should "allow overriding s3_object options with a proc" do + @build_model_with_options[:s3_url_options => lambda {|attachment| { :response_content_type => attachment.avatar_content_type } }] + + rails_env("production") + + @dummy = Dummy.new + + @file = StringIO.new(".") + @file.stubs(:original_filename).returns("5k.png\n\n") + @file.stubs(:content_type).returns("image/png\n\n") + @file.stubs(:to_tempfile).returns(@file) + + @dummy.avatar = @file + + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:url_for).with(:read, :expires => 3600, :secure => true, :response_content_type => "image/png") + + @dummy.avatar.expiring_url + end + end + + context "Generating a url with an expiration for each style" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => { + :production => { :bucket => "prod_bucket" }, + :development => { :bucket => "dev_bucket" } + }, + :s3_permissions => :private, + :s3_host_alias => "something.something.com", + :path => ":attachment/:style/:basename.:extension", + :url => ":s3_alias_url" + + rails_env("production") + + @dummy = Dummy.new + @dummy.avatar = StringIO.new(".") + end + + should "should generate a url for the thumb" do + object = stub + @dummy.avatar.stubs(:s3_object).with(:thumb).returns(object) + object.expects(:url_for).with(:read, :expires => 1800, :secure => true) + @dummy.avatar.expiring_url(1800, :thumb) + end + + should "should generate a url for the default style" do + object = stub + @dummy.avatar.stubs(:s3_object).with(:original).returns(object) + object.expects(:url_for).with(:read, :expires => 1800, :secure => true) + @dummy.avatar.expiring_url(1800) + end + end + + context "Parsing S3 credentials with a bucket in them" do + setup do + rebuild_model :storage => :s3, + :s3_credentials => { + :production => { :bucket => "prod_bucket" }, + :development => { :bucket => "dev_bucket" } + } + @dummy = Dummy.new + end + + should "get the right bucket in production" do + rails_env("production") + assert_equal "prod_bucket", @dummy.avatar.bucket_name + assert_equal "prod_bucket", @dummy.avatar.s3_bucket.name + end + + should "get the right bucket in development" do + rails_env("development") + assert_equal "dev_bucket", @dummy.avatar.bucket_name + assert_equal "dev_bucket", @dummy.avatar.s3_bucket.name + end + end + + context "Parsing S3 credentials with a s3_host_name in them" do + setup do + rebuild_model :storage => :s3, + :bucket => 'testing', + :s3_credentials => { + :production => { :s3_host_name => "s3-world-end.amazonaws.com" }, + :development => { :s3_host_name => "s3-ap-northeast-1.amazonaws.com" } + } + @dummy = Dummy.new + end + + should "get the right s3_host_name in production" do + rails_env("production") + assert_match %r{^s3-world-end.amazonaws.com}, @dummy.avatar.s3_host_name + assert_match %r{^s3-world-end.amazonaws.com}, @dummy.avatar.s3_bucket.config.s3_endpoint + end + + should "get the right s3_host_name in development" do + rails_env("development") + assert_match %r{^s3-ap-northeast-1.amazonaws.com}, @dummy.avatar.s3_host_name + assert_match %r{^s3-ap-northeast-1.amazonaws.com}, @dummy.avatar.s3_bucket.config.s3_endpoint + end + + should "get the right s3_host_name if the key does not exist" do + rails_env("test") + assert_match %r{^s3.amazonaws.com}, @dummy.avatar.s3_host_name + assert_match %r{^s3.amazonaws.com}, @dummy.avatar.s3_bucket.config.s3_endpoint + end + end + + context "An attachment with S3 storage" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + } + end + + should "be extended by the S3 module" do + assert Dummy.new.avatar.is_a?(Paperclip::Storage::S3) + end + + should "not be extended by the Filesystem module" do + assert ! Dummy.new.avatar.is_a?(Paperclip::Storage::Filesystem) + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + should "not get a bucket to get a URL" do + @dummy.avatar.expects(:s3).never + @dummy.avatar.expects(:s3_bucket).never + assert_match %r{^http://s3\.amazonaws\.com/testing/avatars/original/5k\.png}, @dummy.avatar.url + end + + should "be rewinded after flush_writes" do + @dummy.avatar.instance_eval "def after_flush_writes; end" + + files = @dummy.avatar.queued_for_write.values.each(&:read) + @dummy.save + assert files.none?(&:eof?), "Expect all the files to be rewinded." + end + + should "be removed after after_flush_writes" do + paths = @dummy.avatar.queued_for_write.values.map(&:path) + @dummy.save + assert paths.none?{ |path| File.exists?(path) }, + "Expect all the files to be deleted." + end + + context "and saved" do + setup do + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => :public_read) + @dummy.save + end + + should "succeed" do + assert true + end + end + + context "and saved without a bucket" do + setup do + AWS::S3::BucketCollection.any_instance.expects(:create).with("testing") + AWS::S3::S3Object.any_instance.stubs(:write). + raises(AWS::S3::Errors::NoSuchBucket.new(stub, + stub(:status => 404, + :body => ""))). + then.returns(nil) + @dummy.save + end + + should "succeed" do + assert true + end + end + + context "and remove" do + setup do + AWS::S3::S3Object.any_instance.stubs(:exists?).returns(true) + AWS::S3::S3Object.any_instance.stubs(:delete) + @dummy.destroy_attached_files + end + + should "succeed" do + assert true + end + end + + context 'that the file were missing' do + setup do + AWS::S3::S3Object.any_instance.stubs(:exists?).raises(AWS::Errors::Base) + end + + should 'return false on exists?' do + assert !@dummy.avatar.exists? + end + end + end + end + + context "An attachment with S3 storage and bucket defined as a Proc" do + setup do + rebuild_model :storage => :s3, + :bucket => lambda { |attachment| "bucket_#{attachment.instance.other}" }, + :s3_credentials => {:not => :important} + end + + should "get the right bucket name" do + assert "bucket_a", Dummy.new(:other => 'a').avatar.bucket_name + assert "bucket_a", Dummy.new(:other => 'a').avatar.s3_bucket.name + assert "bucket_b", Dummy.new(:other => 'b').avatar.bucket_name + assert "bucket_b", Dummy.new(:other => 'b').avatar.s3_bucket.name + end + end + + context "An attachment with S3 storage and S3 credentials defined as a Proc" do + setup do + rebuild_model :storage => :s3, + :bucket => {:not => :important}, + :s3_credentials => lambda { |attachment| + Hash['access_key_id' => "access#{attachment.instance.other}", 'secret_access_key' => "secret#{attachment.instance.other}"] + } + end + + should "get the right credentials" do + assert "access1234", Dummy.new(:other => '1234').avatar.s3_credentials[:access_key_id] + assert "secret1234", Dummy.new(:other => '1234').avatar.s3_credentials[:secret_access_key] + end + end + + context "An attachment with S3 storage and specific s3 headers set" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + }, + :s3_headers => {'Cache-Control' => 'max-age=31557600'} + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => :public_read, + :cache_control => 'max-age=31557600') + @dummy.save + end + + should "succeed" do + assert true + end + end + end + end + + context "An attachment with S3 storage and metadata set using header names" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + }, + :s3_headers => {'x-amz-meta-color' => 'red'} + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => :public_read, + :metadata => { "color" => "red" }) + @dummy.save + end + + should "succeed" do + assert true + end + end + end + end + + context "An attachment with S3 storage and metadata set using the :s3_metadata option" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + }, + :s3_metadata => { "color" => "red" } + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => :public_read, + :metadata => { "color" => "red" }) + @dummy.save + end + + should "succeed" do + assert true + end + end + end + end + + context "An attachment with S3 storage and storage class set using the header name" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + }, + :s3_headers => { "x-amz-storage-class" => "reduced_redundancy" } + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => :public_read, + :storage_class => "reduced_redundancy") + @dummy.save + end + + should "succeed" do + assert true + end + end + end + end + + context "An attachment with S3 storage and using AES256 encryption" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + }, + :s3_server_side_encryption => :aes256 + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => :public_read, + :server_side_encryption => :aes256) + @dummy.save + end + + should "succeed" do + assert true + end + end + end + end + + context "An attachment with S3 storage and storage class set using the :storage_class option" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + }, + :s3_storage_class => :reduced_redundancy + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => :public_read, + :storage_class => :reduced_redundancy) + @dummy.save + end + + should "succeed" do + assert true + end + end + end + end + + context "with S3 credentials supplied as Pathname" do + setup do + ENV['S3_KEY'] = 'pathname_key' + ENV['S3_BUCKET'] = 'pathname_bucket' + ENV['S3_SECRET'] = 'pathname_secret' + + rails_env('test') + + rebuild_model :storage => :s3, + :s3_credentials => Pathname.new(fixture_file('s3.yml')) + + Dummy.delete_all + @dummy = Dummy.new + end + + should "parse the credentials" do + assert_equal 'pathname_bucket', @dummy.avatar.bucket_name + assert_equal 'pathname_key', @dummy.avatar.s3_bucket.config.access_key_id + assert_equal 'pathname_secret', @dummy.avatar.s3_bucket.config.secret_access_key + end + end + + context "with S3 credentials in a YAML file" do + setup do + ENV['S3_KEY'] = 'env_key' + ENV['S3_BUCKET'] = 'env_bucket' + ENV['S3_SECRET'] = 'env_secret' + + rails_env('test') + + rebuild_model :storage => :s3, + :s3_credentials => File.new(fixture_file('s3.yml')) + + Dummy.delete_all + + @dummy = Dummy.new + end + + should "run the file through ERB" do + assert_equal 'env_bucket', @dummy.avatar.bucket_name + assert_equal 'env_key', @dummy.avatar.s3_bucket.config.access_key_id + assert_equal 'env_secret', @dummy.avatar.s3_bucket.config.secret_access_key + end + end + + context "S3 Permissions" do + context "defaults to :public_read" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + } + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => :public_read) + @dummy.save + end + + should "succeed" do + assert true + end + end + end + end + + context "string permissions set" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + }, + :s3_permissions => :private + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + object = stub + @dummy.avatar.stubs(:s3_object).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => :private) + @dummy.save + end + + should "succeed" do + assert true + end + end + end + end + + context "hash permissions set" do + setup do + rebuild_model :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :styles => { + :thumb => "80x80>" + }, + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + }, + :s3_permissions => { + :original => :private, + :thumb => :public_read + } + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + [:thumb, :original].each do |style| + object = stub + @dummy.avatar.stubs(:s3_object).with(style).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => style == :thumb ? :public_read : :private) + end + @dummy.save + end + + should "succeed" do + assert true + end + end + end + end + + context "proc permission set" do + setup do + rebuild_model( + :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :styles => { + :thumb => "80x80>" + }, + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + }, + :s3_permissions => lambda {|attachment, style| + attachment.instance.private_attachment? && style.to_sym != :thumb ? :private : :public_read + } + ) + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.stubs(:private_attachment? => true) + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + @dummy.save + end + + should "succeed" do + assert @dummy.avatar.url().include? "https://" + assert @dummy.avatar.url(:thumb).include? "http://" + end + end + end + + end + end + + context "An attachment with S3 storage and metadata set using a proc as headers" do + setup do + rebuild_model( + :storage => :s3, + :bucket => "testing", + :path => ":attachment/:style/:basename.:extension", + :styles => { + :thumb => "80x80>" + }, + :s3_credentials => { + 'access_key_id' => "12345", + 'secret_access_key' => "54321" + }, + :s3_headers => lambda {|attachment| + {'Content-Disposition' => "attachment; filename=\"#{attachment.name}\""} + } + ) + end + + context "when assigned" do + setup do + @file = File.new(fixture_file('5k.png'), 'rb') + @dummy = Dummy.new + @dummy.stubs(:name => 'Custom Avatar Name.png') + @dummy.avatar = @file + end + + teardown { @file.close } + + context "and saved" do + setup do + [:thumb, :original].each do |style| + object = stub + @dummy.avatar.stubs(:s3_object).with(style).returns(object) + object.expects(:write).with(anything, + :content_type => "image/png", + :acl => :public_read, + :content_disposition => 'attachment; filename="Custom Avatar Name.png"') + end + @dummy.save + end + + should "succeed" do + assert true + end + end + end + end +end diff --git a/vendor/gems/paperclip/test/style_test.rb b/vendor/gems/paperclip/test/style_test.rb new file mode 100644 index 0000000..a3ec439 --- /dev/null +++ b/vendor/gems/paperclip/test/style_test.rb @@ -0,0 +1,209 @@ +# encoding: utf-8 +require './test/helper' + +class StyleTest < Test::Unit::TestCase + + context "A style rule" do + setup do + @attachment = attachment :path => ":basename.:extension", + :styles => { :foo => {:geometry => "100x100#", :format => :png} }, + :whiny => true + @style = @attachment.styles[:foo] + end + + should "be held as a Style object" do + assert_kind_of Paperclip::Style, @style + end + + should "get processors from the attachment definition" do + assert_equal [:thumbnail], @style.processors + end + + should "have the right geometry" do + assert_equal "100x100#", @style.geometry + end + + should "be whiny if the attachment is" do + assert @style.whiny? + end + + should "respond to hash notation" do + assert_equal [:thumbnail], @style[:processors] + assert_equal "100x100#", @style[:geometry] + end + end + + context "A style rule with properties supplied as procs" do + setup do + @attachment = attachment :path => ":basename.:extension", + :whiny_thumbnails => true, + :processors => lambda {|a| [:test]}, + :styles => { + :foo => lambda{|a| "300x300#"}, + :bar => { + :geometry => lambda{|a| "300x300#"}, + :convert_options => lambda{|a| "-do_stuff"}, + :source_file_options => lambda{|a| "-do_extra_stuff"} + } + } + end + + should "call procs when they are needed" do + assert_equal "300x300#", @attachment.styles[:foo].geometry + assert_equal "300x300#", @attachment.styles[:bar].geometry + assert_equal [:test], @attachment.styles[:foo].processors + assert_equal [:test], @attachment.styles[:bar].processors + assert_equal "-do_stuff", @attachment.styles[:bar].convert_options + assert_equal "-do_extra_stuff", @attachment.styles[:bar].source_file_options + end + end + + context "An attachment with style rules in various forms" do + setup do + styles = {} + styles[:aslist] = ["100x100", :png] + styles[:ashash] = {:geometry => "100x100", :format => :png} + styles[:asstring] = "100x100" + @attachment = attachment :path => ":basename.:extension", + :styles => styles + end + should "have the right number of styles" do + assert_kind_of Hash, @attachment.styles + assert_equal 3, @attachment.styles.size + end + + should "have styles as Style objects" do + [:aslist, :ashash, :aslist].each do |s| + assert_kind_of Paperclip::Style, @attachment.styles[s] + end + end + + should "have the right geometries" do + [:aslist, :ashash, :aslist].each do |s| + assert_equal @attachment.styles[s].geometry, "100x100" + end + end + + should "have the right formats" do + assert_equal @attachment.styles[:aslist].format, :png + assert_equal @attachment.styles[:ashash].format, :png + assert_nil @attachment.styles[:asstring].format + end + + should "retain order" do + assert_equal [:aslist, :ashash, :asstring], @attachment.styles.keys + end + end + + context "An attachment with :convert_options" do + setup do + @attachment = attachment :path => ":basename.:extension", + :styles => {:thumb => "100x100", :large => "400x400"}, + :convert_options => {:all => "-do_stuff", :thumb => "-thumbnailize"} + @style = @attachment.styles[:thumb] + @file = StringIO.new("...") + @file.stubs(:original_filename).returns("file.jpg") + end + + before_should "not have called extra_options_for(:thumb/:large) on initialization" do + @attachment.expects(:extra_options_for).never + end + + should "call extra_options_for(:thumb/:large) when convert options are requested" do + @attachment.expects(:extra_options_for).with(:thumb) + @attachment.styles[:thumb].convert_options + end + end + + context "An attachment with :source_file_options" do + setup do + @attachment = attachment :path => ":basename.:extension", + :styles => {:thumb => "100x100", :large => "400x400"}, + :source_file_options => {:all => "-density 400", :thumb => "-depth 8"} + @style = @attachment.styles[:thumb] + @file = StringIO.new("...") + @file.stubs(:original_filename).returns("file.jpg") + end + + before_should "not have called extra_source_file_options_for(:thumb/:large) on initialization" do + @attachment.expects(:extra_source_file_options_for).never + end + + should "call extra_options_for(:thumb/:large) when convert options are requested" do + @attachment.expects(:extra_source_file_options_for).with(:thumb) + @attachment.styles[:thumb].source_file_options + end + end + + context "A style rule with its own :processors" do + setup do + @attachment = attachment :path => ":basename.:extension", + :styles => { + :foo => { + :geometry => "100x100#", + :format => :png, + :processors => [:test] + } + }, + :processors => [:thumbnail] + @style = @attachment.styles[:foo] + end + + should "not get processors from the attachment" do + @attachment.expects(:processors).never + assert_not_equal [:thumbnail], @style.processors + end + + should "report its own processors" do + assert_equal [:test], @style.processors + end + + end + + context "A style rule with :processors supplied as procs" do + setup do + @attachment = attachment :path => ":basename.:extension", + :styles => { + :foo => { + :geometry => "100x100#", + :format => :png, + :processors => lambda{|a| [:test]} + } + }, + :processors => [:thumbnail] + end + + should "defer processing of procs until they are needed" do + assert_kind_of Proc, @attachment.styles[:foo].instance_variable_get("@processors") + end + + should "call procs when they are needed" do + assert_equal [:test], @attachment.styles[:foo].processors + end + end + + context "An attachment with :convert_options and :source_file_options in :styles" do + setup do + @attachment = attachment :path => ":basename.:extension", + :styles => { + :thumb => "100x100", + :large => {:geometry => "400x400", + :convert_options => "-do_stuff", + :source_file_options => "-do_extra_stuff" + } + } + @file = StringIO.new("...") + @file.stubs(:original_filename).returns("file.jpg") + end + + should "have empty options for :thumb style" do + assert_equal "", @attachment.styles[:thumb].processor_options[:convert_options] + assert_equal "", @attachment.styles[:thumb].processor_options[:source_file_options] + end + + should "have the right options for :large style" do + assert_equal "-do_stuff", @attachment.styles[:large].processor_options[:convert_options] + assert_equal "-do_extra_stuff", @attachment.styles[:large].processor_options[:source_file_options] + end + end +end diff --git a/vendor/gems/paperclip/test/support/mock_attachment.rb b/vendor/gems/paperclip/test/support/mock_attachment.rb new file mode 100644 index 0000000..873df66 --- /dev/null +++ b/vendor/gems/paperclip/test/support/mock_attachment.rb @@ -0,0 +1,22 @@ +class MockAttachment + attr_accessor :updated_at, :original_filename + + def initialize(options = {}) + @model = options[:model] + @responds_to_updated_at = options[:responds_to_updated_at] + @updated_at = options[:updated_at] + @original_filename = options[:original_filename] + end + + def instance + @model + end + + def respond_to?(meth) + if meth.to_s == "updated_at" + @responds_to_updated_at || @updated_at + else + super + end + end +end diff --git a/vendor/gems/paperclip/test/support/mock_interpolator.rb b/vendor/gems/paperclip/test/support/mock_interpolator.rb new file mode 100644 index 0000000..20f33f6 --- /dev/null +++ b/vendor/gems/paperclip/test/support/mock_interpolator.rb @@ -0,0 +1,24 @@ +class MockInterpolator + def initialize(options = {}) + @options = options + end + + def interpolate(pattern, attachment, style_name) + @interpolated_pattern = pattern + @interpolated_attachment = attachment + @interpolated_style_name = style_name + @options[:result] + end + + def has_interpolated_pattern?(pattern) + @interpolated_pattern == pattern + end + + def has_interpolated_style_name?(style_name) + @interpolated_style_name == style_name + end + + def has_interpolated_attachment?(attachment) + @interpolated_attachment == attachment + end +end diff --git a/vendor/gems/paperclip/test/support/mock_model.rb b/vendor/gems/paperclip/test/support/mock_model.rb new file mode 100644 index 0000000..a6e7c12 --- /dev/null +++ b/vendor/gems/paperclip/test/support/mock_model.rb @@ -0,0 +1,2 @@ +class MockModel +end diff --git a/vendor/gems/paperclip/test/support/mock_url_generator_builder.rb b/vendor/gems/paperclip/test/support/mock_url_generator_builder.rb new file mode 100644 index 0000000..2afd7a4 --- /dev/null +++ b/vendor/gems/paperclip/test/support/mock_url_generator_builder.rb @@ -0,0 +1,27 @@ +class MockUrlGeneratorBuilder + def initializer + end + + def new(attachment, attachment_options) + @attachment = attachment + @attachment_options = attachment_options + self + end + + def for(style_name, options) + @generated_url_with_style_name = style_name + @generated_url_with_options = options + "hello" + end + + def has_generated_url_with_options?(options) + # options.is_a_subhash_of(@generated_url_with_options) + options.inject(true) do |acc,(k,v)| + acc && @generated_url_with_options[k] == v + end + end + + def has_generated_url_with_style_name?(style_name) + @generated_url_with_style_name == style_name + end +end diff --git a/vendor/gems/paperclip/test/tempfile_factory_test.rb b/vendor/gems/paperclip/test/tempfile_factory_test.rb new file mode 100644 index 0000000..7c94883 --- /dev/null +++ b/vendor/gems/paperclip/test/tempfile_factory_test.rb @@ -0,0 +1,13 @@ +require './test/helper' + +class Paperclip::TempfileFactoryTest < Test::Unit::TestCase + should "be able to generate a tempfile with the right name" do + file = subject.generate("omg.png") + end + should "be able to generate a tempfile with the right name with a tilde at the beginning" do + file = subject.generate("~omg.png") + end + should "be able to generate a tempfile with the right name with a tilde at the end" do + file = subject.generate("omg.png~") + end +end diff --git a/vendor/gems/paperclip/test/thumbnail_test.rb b/vendor/gems/paperclip/test/thumbnail_test.rb new file mode 100644 index 0000000..a621423 --- /dev/null +++ b/vendor/gems/paperclip/test/thumbnail_test.rb @@ -0,0 +1,446 @@ +require './test/helper' + +class ThumbnailTest < Test::Unit::TestCase + + context "A Paperclip Tempfile" do + setup do + @tempfile = Paperclip::Tempfile.new(["file", ".jpg"]) + end + + teardown { @tempfile.close } + + should "have its path contain a real extension" do + assert_equal ".jpg", File.extname(@tempfile.path) + end + + should "be a real Tempfile" do + assert @tempfile.is_a?(::Tempfile) + end + end + + context "Another Paperclip Tempfile" do + setup do + @tempfile = Paperclip::Tempfile.new("file") + end + + teardown { @tempfile.close } + + should "not have an extension if not given one" do + assert_equal "", File.extname(@tempfile.path) + end + + should "still be a real Tempfile" do + assert @tempfile.is_a?(::Tempfile) + end + end + + context "An image" do + setup do + @file = File.new(fixture_file("5k.png"), 'rb') + end + + teardown { @file.close } + + [["600x600>", "434x66"], + ["400x400>", "400x61"], + ["32x32<", "434x66"] + ].each do |args| + context "being thumbnailed with a geometry of #{args[0]}" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, :geometry => args[0]) + end + + should "start with dimensions of 434x66" do + cmd = %Q[identify -format "%wx%h" "#{@file.path}"] + assert_equal "434x66", `#{cmd}`.chomp + end + + should "report the correct target geometry" do + assert_equal args[0], @thumb.target_geometry.to_s + end + + context "when made" do + setup do + @thumb_result = @thumb.make + end + + should "be the size we expect it to be" do + cmd = %Q[identify -format "%wx%h" "#{@thumb_result.path}"] + assert_equal args[1], `#{cmd}`.chomp + end + end + end + end + + context "being thumbnailed at 100x50 with cropping" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x50#") + end + + should "let us know when a command isn't found versus a processing error" do + old_path = ENV['PATH'] + begin + ENV['PATH'] = '' + assert_raises(Paperclip::Errors::CommandNotFoundError) do + silence_stream(STDERR) do + @thumb.make + end + end + ensure + ENV['PATH'] = old_path + end + end + + should "report its correct current and target geometries" do + assert_equal "100x50#", @thumb.target_geometry.to_s + assert_equal "434x66", @thumb.current_geometry.to_s + end + + should "report its correct format" do + assert_nil @thumb.format + end + + should "have whiny turned on by default" do + assert @thumb.whiny + end + + should "have convert_options set to nil by default" do + assert_equal nil, @thumb.convert_options + end + + should "have source_file_options set to nil by default" do + assert_equal nil, @thumb.source_file_options + end + + should "send the right command to convert when sent #make" do + @thumb.expects(:convert).with do |*arg| + arg[0] == ':source -resize "x50" -crop "100x50+114+0" +repage :dest' && + arg[1][:source] == "#{File.expand_path(@thumb.file.path)}[0]" + end + @thumb.make + end + + should "create the thumbnail when sent #make" do + dst = @thumb.make + assert_match /100x50/, `identify "#{dst.path}"` + end + end + + context "being thumbnailed with source file options set" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, + :geometry => "100x50#", + :source_file_options => "-strip") + end + + should "have source_file_options value set" do + assert_equal ["-strip"], @thumb.source_file_options + end + + should "send the right command to convert when sent #make" do + @thumb.expects(:convert).with do |*arg| + arg[0] == '-strip :source -resize "x50" -crop "100x50+114+0" +repage :dest' && + arg[1][:source] == "#{File.expand_path(@thumb.file.path)}[0]" + end + @thumb.make + end + + should "create the thumbnail when sent #make" do + dst = @thumb.make + assert_match /100x50/, `identify "#{dst.path}"` + end + + context "redefined to have bad source_file_options setting" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, + :geometry => "100x50#", + :source_file_options => "-this-aint-no-option") + end + + should "error when trying to create the thumbnail" do + assert_raises(Paperclip::Error) do + silence_stream(STDERR) do + @thumb.make + end + end + end + end + end + + context "being thumbnailed with convert options set" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, + :geometry => "100x50#", + :convert_options => "-strip -depth 8") + end + + should "have convert_options value set" do + assert_equal %w"-strip -depth 8", @thumb.convert_options + end + + should "send the right command to convert when sent #make" do + @thumb.expects(:convert).with do |*arg| + arg[0] == ':source -resize "x50" -crop "100x50+114+0" +repage -strip -depth 8 :dest' && + arg[1][:source] == "#{File.expand_path(@thumb.file.path)}[0]" + end + @thumb.make + end + + should "create the thumbnail when sent #make" do + dst = @thumb.make + assert_match /100x50/, `identify "#{dst.path}"` + end + + context "redefined to have bad convert_options setting" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, + :geometry => "100x50#", + :convert_options => "-this-aint-no-option") + end + + should "error when trying to create the thumbnail" do + assert_raises(Paperclip::Error) do + silence_stream(STDERR) do + @thumb.make + end + end + end + + should "let us know when a command isn't found versus a processing error" do + old_path = ENV['PATH'] + begin + ENV['PATH'] = '' + assert_raises(Paperclip::Errors::CommandNotFoundError) do + silence_stream(STDERR) do + @thumb.make + end + end + ensure + ENV['PATH'] = old_path + end + end + end + end + + context "being thumbnailed with a blank geometry string" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, + :geometry => "", + :convert_options => "-gravity center -crop \"300x300+0-0\"") + end + + should "not get resized by default" do + assert !@thumb.transformation_command.include?("-resize") + end + end + + context "being thumbnailed with default animated option (true)" do + should "call identify to check for animated images when sent #make" do + thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x50#") + thumb.expects(:identify).at_least_once.with do |*arg| + arg[0] == '-format %m :file' && + arg[1][:file] == "#{File.expand_path(thumb.file.path)}[0]" + end + thumb.make + end + end + + context "passing a custom file geometry parser" do + teardown do + self.class.send(:remove_const, :GeoParser) + end + + should "produce the appropriate transformation_command" do + GeoParser = Class.new do + def self.from_file(file) + new + end + + def transformation_to(target, should_crop) + ["SCALE", "CROP"] + end + end + + thumb = Paperclip::Thumbnail.new(@file, :geometry => '50x50', :file_geometry_parser => GeoParser) + + transformation_command = thumb.transformation_command + + assert transformation_command.include?('-crop'), + %{expected #{transformation_command.inspect} to include '-crop'} + assert transformation_command.include?('"CROP"'), + %{expected #{transformation_command.inspect} to include '"CROP"'} + assert transformation_command.include?('-resize'), + %{expected #{transformation_command.inspect} to include '-resize'} + assert transformation_command.include?('"SCALE"'), + %{expected #{transformation_command.inspect} to include '"SCALE"'} + end + end + + context "passing a custom geometry string parser" do + teardown do + self.class.send(:remove_const, :GeoParser) + end + + should "produce the appropriate transformation_command" do + GeoParser = Class.new do + def self.parse(s) + new + end + + def to_s + "151x167" + end + end + + thumb = Paperclip::Thumbnail.new(@file, :geometry => '50x50', :string_geometry_parser => GeoParser) + + transformation_command = thumb.transformation_command + + assert transformation_command.include?('"151x167"'), + %{expected #{transformation_command.inspect} to include '151x167'} + end + end + end + + context "A multipage PDF" do + setup do + @file = File.new(fixture_file("twopage.pdf"), 'rb') + end + + teardown { @file.close } + + should "start with two pages with dimensions 612x792" do + cmd = %Q[identify -format "%wx%h" "#{@file.path}"] + assert_equal "612x792"*2, `#{cmd}`.chomp + end + + context "being thumbnailed at 100x100 with cropping" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, :geometry => "100x100#", :format => :png) + end + + should "report its correct current and target geometries" do + assert_equal "100x100#", @thumb.target_geometry.to_s + assert_equal "612x792", @thumb.current_geometry.to_s + end + + should "report its correct format" do + assert_equal :png, @thumb.format + end + + should "create the thumbnail when sent #make" do + dst = @thumb.make + assert_match /100x100/, `identify "#{dst.path}"` + end + end + end + + context "An animated gif" do + setup do + @file = File.new(fixture_file("animated.gif"), 'rb') + end + + teardown { @file.close } + + should "start with 12 frames with size 100x100" do + cmd = %Q[identify -format "%wx%h" "#{@file.path}"] + assert_equal "100x100"*12, `#{cmd}`.chomp + end + + context "with static output" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, :geometry => "50x50", :format => :jpg) + end + + should "create the single frame thumbnail when sent #make" do + dst = @thumb.make + cmd = %Q[identify -format "%wx%h" "#{dst.path}"] + assert_equal "50x50", `#{cmd}`.chomp + end + end + + context "with animated output format" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, :geometry => "50x50", :format => :gif) + end + + should "create the 12 frames thumbnail when sent #make" do + dst = @thumb.make + cmd = %Q[identify -format "%wx%h" "#{dst.path}"] + assert_equal "50x50"*12, `#{cmd}`.chomp + end + + should "use the -coalesce option" do + assert_equal @thumb.transformation_command.first, "-coalesce" + end + end + + context "with omitted output format" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, :geometry => "50x50") + end + + should "create the 12 frames thumbnail when sent #make" do + dst = @thumb.make + cmd = %Q[identify -format "%wx%h" "#{dst.path}"] + assert_equal "50x50"*12, `#{cmd}`.chomp + end + + should "use the -coalesce option" do + assert_equal @thumb.transformation_command.first, "-coalesce" + end + end + + context "with unidentified source format" do + setup do + @unidentified_file = File.new(fixture_file("animated.unknown"), 'rb') + @thumb = Paperclip::Thumbnail.new(@file, :geometry => "60x60") + end + + should "create the 12 frames thumbnail when sent #make" do + dst = @thumb.make + cmd = %Q[identify -format "%wx%h" "#{dst.path}"] + assert_equal "60x60"*12, `#{cmd}`.chomp + end + + should "use the -coalesce option" do + assert_equal @thumb.transformation_command.first, "-coalesce" + end + end + + context "with no source format" do + setup do + @unidentified_file = File.new(fixture_file("animated"), 'rb') + @thumb = Paperclip::Thumbnail.new(@file, :geometry => "70x70") + end + + should "create the 12 frames thumbnail when sent #make" do + dst = @thumb.make + cmd = %Q[identify -format "%wx%h" "#{dst.path}"] + assert_equal "70x70"*12, `#{cmd}`.chomp + end + + should "use the -coalesce option" do + assert_equal @thumb.transformation_command.first, "-coalesce" + end + end + + context "with animated option set to false" do + setup do + @thumb = Paperclip::Thumbnail.new(@file, :geometry => "50x50", :animated => false) + end + + should "output the gif format" do + dst = @thumb.make + cmd = %Q[identify "#{dst.path}"] + assert_match /GIF/, `#{cmd}`.chomp + end + + should "create the single frame thumbnail when sent #make" do + dst = @thumb.make + cmd = %Q[identify -format "%wx%h" "#{dst.path}"] + assert_equal "50x50", `#{cmd}`.chomp + end + end + end +end diff --git a/vendor/gems/paperclip/test/url_generator_test.rb b/vendor/gems/paperclip/test/url_generator_test.rb new file mode 100644 index 0000000..6611552 --- /dev/null +++ b/vendor/gems/paperclip/test/url_generator_test.rb @@ -0,0 +1,187 @@ +# encoding: utf-8 +require './test/helper' +require 'paperclip/url_generator' + +class UrlGeneratorTest < Test::Unit::TestCase + should "use the given interpolator" do + expected = "the expected result" + mock_attachment = MockAttachment.new + mock_interpolator = MockInterpolator.new(:result => expected) + + url_generator = Paperclip::UrlGenerator.new(mock_attachment, + { :interpolator => mock_interpolator }) + result = url_generator.for(:style_name, {}) + + assert_equal expected, result + assert mock_interpolator.has_interpolated_attachment?(mock_attachment) + assert mock_interpolator.has_interpolated_style_name?(:style_name) + end + + should "use the default URL when no file is assigned" do + mock_attachment = MockAttachment.new + mock_interpolator = MockInterpolator.new + default_url = "the default url" + options = { :interpolator => mock_interpolator, :default_url => default_url} + + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + url_generator.for(:style_name, {}) + + assert mock_interpolator.has_interpolated_pattern?(default_url), + "expected the interpolator to be passed #{default_url.inspect} but it wasn't" + end + + should "execute the default URL lambda when no file is assigned" do + mock_attachment = MockAttachment.new + mock_interpolator = MockInterpolator.new + default_url = lambda {|attachment| "the #{attachment.class.name} default url" } + options = { :interpolator => mock_interpolator, :default_url => default_url} + + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + url_generator.for(:style_name, {}) + + assert mock_interpolator.has_interpolated_pattern?("the MockAttachment default url"), + %{expected the interpolator to be passed "the MockAttachment default url", but it wasn't} + end + + should "execute the method named by the symbol as the default URL when no file is assigned" do + mock_model = MockModel.new + mock_attachment = MockAttachment.new(:model => mock_model) + mock_interpolator = MockInterpolator.new + default_url = :to_s + options = { :interpolator => mock_interpolator, :default_url => default_url} + + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + url_generator.for(:style_name, {}) + + assert mock_interpolator.has_interpolated_pattern?(mock_model.to_s), + %{expected the interpolator to be passed #{mock_model.to_s}, but it wasn't} + end + + should "URL-escape spaces if asked to" do + expected = "the expected result" + mock_attachment = MockAttachment.new + mock_interpolator = MockInterpolator.new(:result => expected) + options = { :interpolator => mock_interpolator } + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + + result = url_generator.for(:style_name, {:escape => true}) + + assert_equal "the%20expected%20result", result + end + + should "escape the result of the interpolator using a method on the object, if asked to escape" do + expected = Class.new do + def escape + "the escaped result" + end + end.new + mock_attachment = MockAttachment.new + mock_interpolator = MockInterpolator.new(:result => expected) + options = { :interpolator => mock_interpolator } + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + + result = url_generator.for(:style_name, {:escape => true}) + + assert_equal "the escaped result", result + end + + should "leave spaces unescaped as asked to" do + expected = "the expected result" + mock_attachment = MockAttachment.new + mock_interpolator = MockInterpolator.new(:result => expected) + options = { :interpolator => mock_interpolator } + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + + result = url_generator.for(:style_name, {:escape => false}) + + assert_equal "the expected result", result + end + + should "default to leaving spaces unescaped" do + expected = "the expected result" + mock_attachment = MockAttachment.new + mock_interpolator = MockInterpolator.new(:result => expected) + options = { :interpolator => mock_interpolator } + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + + result = url_generator.for(:style_name, {}) + + assert_equal "the expected result", result + end + + should "produce URLs without the updated_at value when the object does not respond to updated_at" do + expected = "the expected result" + mock_interpolator = MockInterpolator.new(:result => expected) + mock_attachment = MockAttachment.new(:responds_to_updated_at => false) + options = { :interpolator => mock_interpolator } + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + + result = url_generator.for(:style_name, {:timestamp => true}) + + assert_equal expected, result + end + + should "produce URLs without the updated_at value when the updated_at value is nil" do + expected = "the expected result" + mock_interpolator = MockInterpolator.new(:result => expected) + mock_attachment = MockAttachment.new(:responds_to_updated_at => true, :updated_at => nil) + options = { :interpolator => mock_interpolator } + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + + result = url_generator.for(:style_name, {:timestamp => true}) + + assert_equal expected, result + end + + should "produce URLs with the updated_at when it exists" do + expected = "the expected result" + updated_at = 1231231234 + mock_interpolator = MockInterpolator.new(:result => expected) + mock_attachment = MockAttachment.new(:updated_at => updated_at) + options = { :interpolator => mock_interpolator } + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + + result = url_generator.for(:style_name, {:timestamp => true}) + + assert_equal "#{expected}?#{updated_at}", result + end + + should "produce URLs with the updated_at when it exists, separated with a & if a ? follow by = already exists" do + expected = "the?expected=result" + updated_at = 1231231234 + mock_interpolator = MockInterpolator.new(:result => expected) + mock_attachment = MockAttachment.new(:updated_at => updated_at) + options = { :interpolator => mock_interpolator } + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + + result = url_generator.for(:style_name, {:timestamp => true}) + + assert_equal "#{expected}&#{updated_at}", result + end + + should "produce URLs without the updated_at when told to do as much" do + expected = "the expected result" + updated_at = 1231231234 + mock_interpolator = MockInterpolator.new(:result => expected) + mock_attachment = MockAttachment.new(:updated_at => updated_at) + options = { :interpolator => mock_interpolator } + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + + result = url_generator.for(:style_name, {:timestamp => false}) + + assert_equal expected, result + end + + should "produce the correct URL when the instance has a file name" do + expected = "the expected result" + mock_attachment = MockAttachment.new(:original_filename => 'exists') + mock_interpolator = MockInterpolator.new + options = { :interpolator => mock_interpolator, :url => expected} + + url_generator = Paperclip::UrlGenerator.new(mock_attachment, options) + url_generator.for(:style_name, {}) + + assert mock_interpolator.has_interpolated_pattern?(expected), + "expected the interpolator to be passed #{expected.inspect} but it wasn't" + end +end diff --git a/vendor/gems/paperclip/test/validators/attachment_content_type_validator_test.rb b/vendor/gems/paperclip/test/validators/attachment_content_type_validator_test.rb new file mode 100644 index 0000000..12b230f --- /dev/null +++ b/vendor/gems/paperclip/test/validators/attachment_content_type_validator_test.rb @@ -0,0 +1,192 @@ +require './test/helper' + +class AttachmentContentTypeValidatorTest < Test::Unit::TestCase + def setup + rebuild_model + @dummy = Dummy.new + end + + def build_validator(options) + @validator = Paperclip::Validators::AttachmentContentTypeValidator.new(options.merge( + :attributes => :avatar + )) + end + + context "with a nil content type" do + setup do + build_validator :content_type => "image/jpg" + @dummy.stubs(:avatar_content_type => nil) + @validator.validate(@dummy) + end + + should "not set an error message" do + assert @dummy.errors[:avatar_content_type].blank? + end + end + + context "with :allow_nil option" do + context "as true" do + setup do + build_validator :content_type => "image/png", :allow_nil => true + @dummy.stubs(:avatar_content_type => nil) + @validator.validate(@dummy) + end + + should "allow avatar_content_type as nil" do + assert @dummy.errors[:avatar_content_type].blank? + end + end + + context "as false" do + setup do + build_validator :content_type => "image/png", :allow_nil => false + @dummy.stubs(:avatar_content_type => nil) + @validator.validate(@dummy) + end + + should "not allow avatar_content_type as nil" do + assert @dummy.errors[:avatar_content_type].present? + end + end + end + + context "with :allow_blank option" do + context "as true" do + setup do + build_validator :content_type => "image/png", :allow_blank => true + @dummy.stubs(:avatar_content_type => "") + @validator.validate(@dummy) + end + + should "allow avatar_content_type as blank" do + assert @dummy.errors[:avatar_content_type].blank? + end + end + + context "as false" do + setup do + build_validator :content_type => "image/png", :allow_blank => false + @dummy.stubs(:avatar_content_type => "") + @validator.validate(@dummy) + end + + should "not allow avatar_content_type as blank" do + assert @dummy.errors[:avatar_content_type].present? + end + end + end + + context "with an allowed type" do + context "as a string" do + setup do + build_validator :content_type => "image/jpg" + @dummy.stubs(:avatar_content_type => "image/jpg") + @validator.validate(@dummy) + end + + should "not set an error message" do + assert @dummy.errors[:avatar_content_type].blank? + end + end + + context "as an regexp" do + setup do + build_validator :content_type => /^image\/.*/ + @dummy.stubs(:avatar_content_type => "image/jpg") + @validator.validate(@dummy) + end + + should "not set an error message" do + assert @dummy.errors[:avatar_content_type].blank? + end + end + + context "as a list" do + setup do + build_validator :content_type => ["image/png", "image/jpg", "image/jpeg"] + @dummy.stubs(:avatar_content_type => "image/jpg") + @validator.validate(@dummy) + end + + should "not set an error message" do + assert @dummy.errors[:avatar_content_type].blank? + end + end + end + + context "with a disallowed type" do + context "as a string" do + setup do + build_validator :content_type => "image/png" + @dummy.stubs(:avatar_content_type => "image/jpg") + @validator.validate(@dummy) + end + + should "set a correct default error message" do + assert @dummy.errors[:avatar_content_type].present? + assert_includes @dummy.errors[:avatar_content_type], "is invalid" + end + end + + context "as a regexp" do + setup do + build_validator :content_type => /^text\/.*/ + @dummy.stubs(:avatar_content_type => "image/jpg") + @validator.validate(@dummy) + end + + should "set a correct default error message" do + assert @dummy.errors[:avatar_content_type].present? + assert_includes @dummy.errors[:avatar_content_type], "is invalid" + end + end + + context "with :message option" do + context "without interpolation" do + setup do + build_validator :content_type => "image/png", :message => "should be a PNG image" + @dummy.stubs(:avatar_content_type => "image/jpg") + @validator.validate(@dummy) + end + + should "set a correct error message" do + assert_includes @dummy.errors[:avatar_content_type], "should be a PNG image" + end + end + + context "with interpolation" do + setup do + build_validator :content_type => "image/png", :message => "should have content type %{types}" + @dummy.stubs(:avatar_content_type => "image/jpg") + @validator.validate(@dummy) + end + + should "set a correct error message" do + assert_includes @dummy.errors[:avatar_content_type], "should have content type image/png" + end + end + end + end + + context "using the helper" do + setup do + Dummy.validates_attachment_content_type :avatar, :content_type => "image/jpg" + end + + should "add the validator to the class" do + assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_content_type } + end + end + + context "given options" do + should "raise argument error if no required argument was given" do + assert_raises(ArgumentError) do + build_validator :message => "Some message" + end + end + + should "not raise arguemnt error if :content_type was given" do + build_validator :content_type => "image/jpg" + end + end +end diff --git a/vendor/gems/paperclip/test/validators/attachment_presence_validator_test.rb b/vendor/gems/paperclip/test/validators/attachment_presence_validator_test.rb new file mode 100644 index 0000000..9ae9304 --- /dev/null +++ b/vendor/gems/paperclip/test/validators/attachment_presence_validator_test.rb @@ -0,0 +1,85 @@ +require './test/helper' + +class AttachmentPresenceValidatorTest < Test::Unit::TestCase + def setup + rebuild_model + @dummy = Dummy.new + end + + def build_validator(options={}) + @validator = Paperclip::Validators::AttachmentPresenceValidator.new(options.merge( + :attributes => :avatar + )) + end + + context "nil attachment" do + setup do + @dummy.avatar = nil + end + + context "with default options" do + setup do + build_validator + @validator.validate(@dummy) + end + + should "add error on the attachment" do + assert @dummy.errors[:avatar].present? + end + + should "not add an error on the file_name attribute" do + assert @dummy.errors[:avatar_file_name].blank? + end + end + + context "with :if option" do + context "returning true" do + setup do + build_validator :if => true + @validator.validate(@dummy) + end + + should "perform a validation" do + assert @dummy.errors[:avatar].present? + end + end + + context "returning false" do + setup do + build_validator :if => false + @validator.validate(@dummy) + end + + should "perform a validation" do + assert @dummy.errors[:avatar].present? + end + end + end + end + + context "with attachment" do + setup do + build_validator + @dummy.avatar = StringIO.new('.') + @validator.validate(@dummy) + end + + should "not add error on the attachment" do + assert @dummy.errors[:avatar].blank? + end + + should "not add an error on the file_name attribute" do + assert @dummy.errors[:avatar_file_name].blank? + end + end + + context "using the helper" do + setup do + Dummy.validates_attachment_presence :avatar + end + + should "add the validator to the class" do + assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_presence } + end + end +end diff --git a/vendor/gems/paperclip/test/validators/attachment_size_validator_test.rb b/vendor/gems/paperclip/test/validators/attachment_size_validator_test.rb new file mode 100644 index 0000000..0def809 --- /dev/null +++ b/vendor/gems/paperclip/test/validators/attachment_size_validator_test.rb @@ -0,0 +1,207 @@ +require './test/helper' + +class AttachmentSizeValidatorTest < Test::Unit::TestCase + def setup + rebuild_model + @dummy = Dummy.new + end + + def build_validator(options) + @validator = Paperclip::Validators::AttachmentSizeValidator.new(options.merge( + :attributes => :avatar + )) + end + + def self.should_allow_attachment_file_size(size) + context "when the attachment size is #{size}" do + should "add error to dummy object" do + @dummy.stubs(:avatar_file_size).returns(size) + @validator.validate(@dummy) + assert @dummy.errors[:avatar_file_size].blank?, + "Expect an error message on :avatar_file_size, got none." + end + end + end + + def self.should_not_allow_attachment_file_size(size, options = {}) + context "when the attachment size is #{size}" do + setup do + @dummy.stubs(:avatar_file_size).returns(size) + @validator.validate(@dummy) + end + + should "add error to dummy object" do + assert @dummy.errors[:avatar_file_size].present?, + "Unexpected error message on :avatar_file_size" + end + + if options[:message] + should "return a correct error message" do + assert_includes @dummy.errors[:avatar_file_size], options[:message] + end + end + end + end + + context "with :in option" do + context "as a range" do + setup do + build_validator :in => (5.kilobytes..10.kilobytes) + end + + should_allow_attachment_file_size(7.kilobytes) + should_not_allow_attachment_file_size(4.kilobytes) + should_not_allow_attachment_file_size(11.kilobytes) + end + + context "as a proc" do + setup do + build_validator :in => lambda { |avatar| (5.kilobytes..10.kilobytes) } + end + + should_allow_attachment_file_size(7.kilobytes) + should_not_allow_attachment_file_size(4.kilobytes) + should_not_allow_attachment_file_size(11.kilobytes) + end + end + + context "with :greater_than option" do + context "as number" do + setup do + build_validator :greater_than => 10.kilobytes + end + + should_allow_attachment_file_size 11.kilobytes + should_not_allow_attachment_file_size 10.kilobytes + end + + context "as a proc" do + setup do + build_validator :greater_than => lambda { |avatar| 10.kilobytes } + end + + should_allow_attachment_file_size 11.kilobytes + should_not_allow_attachment_file_size 10.kilobytes + end + end + + context "with :less_than option" do + context "as number" do + setup do + build_validator :less_than => 10.kilobytes + end + + should_allow_attachment_file_size 9.kilobytes + should_not_allow_attachment_file_size 10.kilobytes + end + + context "as a proc" do + setup do + build_validator :less_than => lambda { |avatar| 10.kilobytes } + end + + should_allow_attachment_file_size 9.kilobytes + should_not_allow_attachment_file_size 10.kilobytes + end + end + + context "with :greater_than and :less_than option" do + context "as numbers" do + setup do + build_validator :greater_than => 5.kilobytes, + :less_than => 10.kilobytes + end + + should_allow_attachment_file_size 7.kilobytes + should_not_allow_attachment_file_size 5.kilobytes + should_not_allow_attachment_file_size 10.kilobytes + end + + context "as a proc" do + setup do + build_validator :greater_than => lambda { |avatar| 5.kilobytes }, + :less_than => lambda { |avatar| 10.kilobytes } + end + + should_allow_attachment_file_size 7.kilobytes + should_not_allow_attachment_file_size 5.kilobytes + should_not_allow_attachment_file_size 10.kilobytes + end + end + + context "with :message option" do + context "given a range" do + setup do + build_validator :in => (5.kilobytes..10.kilobytes), + :message => "is invalid. (Between %{min} and %{max} please.)" + end + + should_not_allow_attachment_file_size 11.kilobytes, + :message => "is invalid. (Between 5120 Bytes and 10240 Bytes please.)" + end + + context "given :less_than and :greater_than" do + setup do + build_validator :less_than => 10.kilobytes, + :greater_than => 5.kilobytes, + :message => "is invalid. (Between %{min} and %{max} please.)" + end + + should_not_allow_attachment_file_size 11.kilobytes, + :message => "is invalid. (Between 5120 Bytes and 10240 Bytes please.)" + end + end + + context "default error messages" do + context "given :less_than and :greater_than" do + setup do + build_validator :greater_than => 5.kilobytes, + :less_than => 10.kilobytes + end + + should_not_allow_attachment_file_size 11.kilobytes, + :message => "must be less than 10240 Bytes" + should_not_allow_attachment_file_size 4.kilobytes, + :message => "must be greater than 5120 Bytes" + end + + context "given a size range" do + setup do + build_validator :in => (5.kilobytes..10.kilobytes) + end + + should_not_allow_attachment_file_size 11.kilobytes, + :message => "must be in between 5120 Bytes and 10240 Bytes" + should_not_allow_attachment_file_size 4.kilobytes, + :message => "must be in between 5120 Bytes and 10240 Bytes" + end + end + + context "using the helper" do + setup do + Dummy.validates_attachment_size :avatar, :in => (5.kilobytes..10.kilobytes) + end + + should "add the validator to the class" do + assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_size } + end + end + + context "given options" do + should "raise argument error if no required argument was given" do + assert_raises(ArgumentError) do + build_validator :message => "Some message" + end + end + + (Paperclip::Validators::AttachmentSizeValidator::AVAILABLE_CHECKS).each do |argument| + should "not raise arguemnt error if #{argument} was given" do + build_validator argument => 5.kilobytes + end + end + + should "not raise argument error if :in was given" do + build_validator :in => (5.kilobytes..10.kilobytes) + end + end +end diff --git a/vendor/gems/paperclip/test/validators_test.rb b/vendor/gems/paperclip/test/validators_test.rb new file mode 100644 index 0000000..4c2567a --- /dev/null +++ b/vendor/gems/paperclip/test/validators_test.rb @@ -0,0 +1,25 @@ +require './test/helper' + +class ValidatorsTest < Test::Unit::TestCase + def setup + rebuild_model + end + + context "using the helper" do + setup do + Dummy.validates_attachment :avatar, :presence => true, :content_type => { :content_type => "image/jpg" }, :size => { :in => 0..10.kilobytes } + end + + should "add the attachment_presence validator to the class" do + assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_presence } + end + + should "add the attachment_content_type validator to the class" do + assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_content_type } + end + + should "add the attachment_size validator to the class" do + assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_size } + end + end +end