Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge remote branch 'thoughtbot/master' into aws-sdk-backend

Conflicts:
	Gemfile
	Gemfile.lock
	features/support/s3.rb
	gemfiles/rails2.gemfile
	gemfiles/rails2.gemfile.lock
	gemfiles/rails3.gemfile
	gemfiles/rails3.gemfile.lock
	gemfiles/rails3_1.gemfile
	gemfiles/rails3_1.gemfile.lock
	lib/paperclip/storage/s3.rb
	test/storage/s3_test.rb
  • Loading branch information...
commit 5f5369dd8aa5b8d0f02a75f03d6e5f114bafc7ae 2 parents b67eaa7 + 47a56c2
Trevor Rowe trevorrowe authored

Showing 64 changed files with 1,614 additions and 1,102 deletions. Show diff stats Hide diff stats

  1. +1 0  .gitignore
  2. +4 2 .travis.yml
  3. +6 3 Appraisals
  4. +5 5 Gemfile
  5. +0 102 Gemfile.lock
  6. +63 5 README.md
  7. +8 2 Rakefile
  8. +0 17 features/basic.feature
  9. +46 0 features/basic_integration.feature
  10. +0 27 features/s3.feature
  11. +1 0  features/step_definitions/html_steps.rb
  12. +136 54 features/step_definitions/rails_steps.rb
  13. +12 7 features/step_definitions/s3_steps.rb
  14. +88 106 features/step_definitions/web_steps.rb
  15. +5 0 features/support/env.rb
  16. +3 0  features/support/fakeweb.rb
  17. BIN  features/support/fixtures/.boot_config.rb.swo
  18. +15 0 features/support/fixtures/boot_config.txt
  19. +5 0 features/support/fixtures/gemfile.txt
  20. +20 0 features/support/fixtures/preinitializer.txt
  21. +2 9 features/support/paths.rb
  22. +39 2 features/support/rails.rb
  23. +0 22 features/support/s3.rb
  24. +19 0 features/support/selectors.rb
  25. +0 20 gemfiles/rails2.gemfile
  26. +0 86 gemfiles/rails2.gemfile.lock
  27. +0 20 gemfiles/rails3.gemfile
  28. +0 129 gemfiles/rails3.gemfile.lock
  29. +0 20 gemfiles/rails3_1.gemfile
  30. +0 135 gemfiles/rails3_1.gemfile.lock
  31. +77 25 lib/paperclip.rb
  32. +56 58 lib/paperclip/attachment.rb
  33. +6 4 lib/paperclip/geometry.rb
  34. +33 0 lib/paperclip/interpolated_string.rb
  35. +15 4 lib/paperclip/interpolations.rb
  36. +87 0 lib/paperclip/missing_attachment_styles.rb
  37. +80 0 lib/paperclip/options.rb
  38. +2 0  lib/paperclip/railtie.rb
  39. +3 0  lib/paperclip/storage/filesystem.rb
  40. +24 15 lib/paperclip/storage/fog.rb
  41. +91 45 lib/paperclip/storage/s3.rb
  42. +3 3 lib/paperclip/style.rb
  43. +1 1  lib/paperclip/upfile.rb
  44. +1 1  lib/paperclip/version.rb
  45. +16 0 lib/tasks/paperclip.rake
  46. +2 0  paperclip.gemspec
  47. +8 2 shoulda_macros/paperclip.rb
  48. +43 30 test/attachment_test.rb
  49. BIN  test/fixtures/spaced file.png
  50. +1 0  test/fixtures/text.txt
  51. +11 4 test/fog_test.rb
  52. +1 1  test/geometry_test.rb
  53. +4 1 test/helper.rb
  54. +16 0 test/integration_test.rb
  55. +62 0 test/interpolated_string_test.rb
  56. +15 1 test/interpolations_test.rb
  57. +75 0 test/options_test.rb
  58. +80 0 test/paperclip_missing_attachment_styles_test.rb
  59. +65 5 test/paperclip_test.rb
  60. +56 0 test/storage/filesystem_test.rb
  61. +88 0 test/storage/s3_live_test.rb
  62. +76 93 test/{storage_test.rb → storage/s3_test.rb}
  63. +22 28 test/style_test.rb
  64. +16 8 test/upfile_test.rb
1  .gitignore
@@ -10,3 +10,4 @@ capybara*.html
10 10 *SPIKE*
11 11 .rvmrc
12 12 *emfile.lock
  13 +.rbx
6 .travis.yml
@@ -3,8 +3,10 @@ rvm:
3 3 - 1.9.2
4 4 - ree
5 5 - rbx-2.0
6   -
7   -script: "bundle exec rake clean test"
  6 +
  7 +before_script: "sudo ntpdate -ub ntp.ubuntu.com pool.ntp.org; true"
  8 +script: "bundle exec rake clean test cucumber"
  9 +
8 10 gemfile:
9 11 - gemfiles/rails2.gemfile
10 12 - gemfiles/rails3.gemfile
9 Appraisals
... ... @@ -1,11 +1,14 @@
1 1 appraise "rails2" do
2   - gem "rails", "~> 2.3.12"
  2 + gem "rails", "~> 2.3.14"
  3 + gem "paperclip", :path => "../"
3 4 end
4 5
5 6 appraise "rails3" do
6   - gem "rails", "~> 3.0.9"
  7 + gem "rails", "~> 3.0.10"
  8 + gem "paperclip", :path => "../"
7 9 end
8 10
9 11 appraise "rails3_1" do
10   - gem "rails", "~> 3.1.0.rc5"
  12 + gem "rails", "~> 3.1.0"
  13 + gem "paperclip", :path => "../"
11 14 end
10 Gemfile
@@ -2,6 +2,7 @@ source "http://rubygems.org"
2 2
3 3 gem "activerecord", :require => "active_record"
4 4 gem "appraisal"
  5 +gem "aruba"
5 6 gem "aws-sdk", "~>1.0"
6 7 gem "bundler"
7 8 gem "cocaine", "~>0.2"
@@ -11,11 +12,10 @@ gem "mime-types"
11 12 gem "mocha"
12 13 gem "rake"
13 14 gem "rdoc", :require => false
  15 +gem "capybara"
  16 +gem "cucumber", "~> 1.0.0"
14 17 gem "shoulda"
15 18 gem "sqlite3", "~>1.3.4"
  19 +gem "fakeweb", :require => false
16 20
17   -# This is for Rails 3.1
18   -gem "sprockets", "~> 2.0.0.beta.13", :require => false
19   -
20   -# gem "ruby-debug", :platform => :ruby_18
21   -# gem "ruby-debug19", :platform => :ruby_19
  21 +gem 'pry'
102 Gemfile.lock
... ... @@ -1,102 +0,0 @@
1   -GEM
2   - remote: http://rubygems.org/
3   - specs:
4   - activerecord (2.3.12)
5   - activesupport (= 2.3.12)
6   - activesupport (2.3.12)
7   - appraisal (0.3.5)
8   - aruba (~> 0.3.6)
9   - bundler
10   - rake
11   - aruba (0.3.7)
12   - childprocess (>= 0.1.9)
13   - cucumber (>= 0.10.5)
14   - rspec (>= 2.6.0)
15   - aws-sdk (1.1.2)
16   - httparty (~> 0.7)
17   - json (~> 1.4)
18   - nokogiri (>= 1.4.4)
19   - uuidtools (~> 2.1)
20   - bouncy-castle-java (1.5.0146.1)
21   - builder (3.0.0)
22   - childprocess (0.1.9)
23   - ffi (~> 1.0.6)
24   - cocaine (0.2.0)
25   - crack (0.1.8)
26   - cucumber (0.10.5)
27   - builder (>= 2.1.2)
28   - diff-lcs (>= 1.1.2)
29   - gherkin (~> 2.4.0)
30   - json (>= 1.4.6)
31   - term-ansicolor (>= 1.0.5)
32   - diff-lcs (1.1.2)
33   - excon (0.6.3)
34   - ffi (1.0.9)
35   - ffi (1.0.9-java)
36   - fog (0.8.2)
37   - builder
38   - excon (~> 0.6.1)
39   - formatador (>= 0.1.3)
40   - json
41   - mime-types
42   - net-ssh (>= 2.1.3)
43   - nokogiri (>= 1.4.4)
44   - ruby-hmac
45   - formatador (0.1.4)
46   - gherkin (2.4.5)
47   - json (>= 1.4.6)
48   - gherkin (2.4.5-java)
49   - json (>= 1.4.6)
50   - hike (1.2.0)
51   - httparty (0.7.8)
52   - crack (= 0.1.8)
53   - jruby-openssl (0.7.4)
54   - bouncy-castle-java
55   - json (1.5.3)
56   - json (1.5.3-java)
57   - mime-types (1.16)
58   - mocha (0.9.12)
59   - net-ssh (2.1.4)
60   - nokogiri (1.5.0)
61   - nokogiri (1.5.0-java)
62   - rack (1.3.2)
63   - rake (0.9.2)
64   - rdoc (3.8)
65   - rspec (2.6.0)
66   - rspec-core (~> 2.6.0)
67   - rspec-expectations (~> 2.6.0)
68   - rspec-mocks (~> 2.6.0)
69   - rspec-core (2.6.4)
70   - rspec-expectations (2.6.0)
71   - diff-lcs (~> 1.1.2)
72   - rspec-mocks (2.6.0)
73   - ruby-hmac (0.4.0)
74   - shoulda (2.11.3)
75   - sprockets (2.0.0.beta.13)
76   - hike (~> 1.2)
77   - rack (~> 1.0)
78   - tilt (~> 1.1, != 1.3.0)
79   - sqlite3 (1.3.4)
80   - term-ansicolor (1.0.5)
81   - tilt (1.3.2)
82   - uuidtools (2.1.2)
83   -
84   -PLATFORMS
85   - java
86   - ruby
87   -
88   -DEPENDENCIES
89   - activerecord
90   - appraisal
91   - aws-sdk (~> 1.0)
92   - bundler
93   - cocaine (~> 0.2)
94   - fog
95   - jruby-openssl
96   - mime-types
97   - mocha
98   - rake
99   - rdoc
100   - shoulda
101   - sprockets (~> 2.0.0.beta.13)
102   - sqlite3 (~> 1.3.4)
68 README.md
Source Rendered
@@ -49,7 +49,7 @@ well with gems.
49 49
50 50 Include the gem in your Gemfile:
51 51
52   - gem "paperclip", "~> 2.3"
  52 + gem "paperclip", "~> 2.4"
53 53
54 54 Or, if you don't use Bundler (though you probably should, even in Rails 2), with config.gem
55 55
@@ -57,9 +57,15 @@ Or, if you don't use Bundler (though you probably should, even in Rails 2), with
57 57 ...
58 58 Rails::Initializer.run do |config|
59 59 ...
60   - config.gem "paperclip", :version => "~> 2.3"
  60 + config.gem "paperclip", :version => "~> 2.4"
61 61 ...
62 62 end
  63 +For Non-Rails usage:
  64 +
  65 + class ModuleName < ActiveRecord::Base
  66 + include Paperclip::Glue
  67 + ...
  68 + end
63 69
64 70 Quick Start
65 71 -----------
@@ -203,7 +209,7 @@ styles, no processors will be run if there are no styles defined._
203 209 If you're interested in caching your thumbnail's width, height and size in the
204 210 database, take a look at the [paperclip-meta](https://github.com/y8/paperclip-meta) gem.
205 211
206   -Also, if you're interesting to generate the thumbnail on-the-fly, you might want
  212 +Also, if you're interested in generating the thumbnail on-the-fly, you might want
207 213 to look into the [attachment_on_the_fly](https://github.com/drpentode/Attachment-on-the-Fly) gem.
208 214
209 215 Events
@@ -227,7 +233,9 @@ called with valid attachments._
227 233 URI Obfuscation
228 234 ---------------
229 235
230   -Paperclip has an interpolation called `:hash` for obfuscating filenames of publicly-available files. For more on this feature read author's own explanation.
  236 +Paperclip has an interpolation called `:hash` for obfuscating filenames of
  237 +publicly-available files. For more on this feature read the author's own
  238 +explanation.
231 239
232 240 [https://github.com/thoughtbot/paperclip/pull/416](https://github.com/thoughtbot/paperclip/pull/416)
233 241
@@ -269,7 +277,7 @@ a few utility examples would be compression and encryption processors.
269 277 Dynamic Configuration
270 278 ---------------------
271 279
272   -Callable objects (labdas, Procs) can be used in a number of places for dynamic
  280 +Callable objects (lambdas, Procs) can be used in a number of places for dynamic
273 281 configuration throughout Paperclip. This strategy exists in a number of
274 282 components of the library but is most significant in the possibilities for
275 283 allowing custom styles and processors to be applied for specific model
@@ -305,6 +313,56 @@ processors, where a defined `watermark` processor is invoked after the
305 313 attr_accessor :watermark
306 314 end
307 315
  316 +Deploy
  317 +------
  318 +
  319 +Paperclip is aware of new attachment styles you have added in previous deploy. The only thing you should do after each deployment is to call
  320 +`rake paperclip:refresh:missing_styles`. It will store current attachment styles in `RAILS_ROOT/public/system/paperclip_attachments.yml`
  321 +by default. You can change it by:
  322 +
  323 + Paperclip.registered_attachments_styles_path = '/tmp/config/paperclip_attachments.yml'
  324 +
  325 +Here is an example for Capistrano:
  326 +
  327 + namespace :deploy do
  328 + desc "build missing paperclip styles"
  329 + task :build_missing_paperclip_styles, :roles => :app do
  330 + run "cd #{release_path}; RAILS_ENV=production bundle exec rake paperclip:refresh:missing_styles"
  331 + end
  332 + end
  333 +
  334 + after("deploy:update_code", "deploy:build_missing_paperclip_styles")
  335 +
  336 +Now you don't have to remember to refresh thumbnails in production everytime you add new style.
  337 +Unfortunately it does not work with dynamic styles - it just ignores them.
  338 +
  339 +If you already have working app and don't want `rake paperclip:refresh:missing_styles` to refresh old pictures, you need to tell
  340 +Paperclip about existing styles. Simply create paperclip_attachments.yml file by hand. For example:
  341 +
  342 + class User < ActiveRecord::Base
  343 + has_attached_file :avatar, :styles => {:thumb => 'x100', :croppable => '600x600>', :big => '1000x1000>'}
  344 + end
  345 +
  346 + class Book < ActiveRecord::Base
  347 + has_attached_file :cover, :styles => {:small => 'x100', :large => '1000x1000>'}
  348 + has_attached_file :sample, :styles => {:thumb => 'x100'}
  349 + end
  350 +
  351 +Then in `RAILS_ROOT/public/system/paperclip_attachments.yml`:
  352 +
  353 + ---
  354 + :User:
  355 + :avatar:
  356 + - :thumb
  357 + - :croppable
  358 + - :big
  359 + :Book:
  360 + :cover:
  361 + - :small
  362 + - :large
  363 + :sample:
  364 + - :thumb
  365 +
308 366 Testing
309 367 -------
310 368
10 Rakefile
... ... @@ -1,10 +1,11 @@
1 1 require 'rubygems'
2   -require 'appraisal'
3 2 require 'bundler/setup'
  3 +require 'appraisal'
4 4
5 5 require 'rake'
6 6 require 'rake/testtask'
7 7 require 'rdoc/task'
  8 +require 'cucumber/rake/task'
8 9
9 10 $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
10 11 require 'paperclip'
@@ -14,7 +15,7 @@ task :default => [:clean, 'appraisal:install', :all]
14 15
15 16 desc 'Test the paperclip plugin under all supported Rails versions.'
16 17 task :all do |t|
17   - exec('rake appraisal test')
  18 + exec('rake appraisal test cucumber')
18 19 end
19 20
20 21 desc 'Test the paperclip plugin.'
@@ -24,6 +25,11 @@ Rake::TestTask.new(:test) do |t|
24 25 t.verbose = true
25 26 end
26 27
  28 +desc 'Run integration test'
  29 +Cucumber::Rake::Task.new do |t|
  30 + t.cucumber_opts = %w{--format progress}
  31 +end
  32 +
27 33 desc 'Start an IRB session with all necessary files required.'
28 34 task :shell do |t|
29 35 chdir File.dirname(__FILE__)
17 features/basic.feature
... ... @@ -1,17 +0,0 @@
1   -Feature: Running paperclip in a Rails app
2   -
3   - Scenario: Basic utilization
4   - Given I have a rails application
5   - And I save the following as "app/models/user.rb"
6   - """
7   - class User < ActiveRecord::Base
8   - has_attached_file :avatar
9   - end
10   - """
11   - When I visit /users/new
12   - And I fill in "user_name" with "something"
13   - And I attach the file "test/fixtures/5k.png" to "user_avatar"
14   - And I press "Submit"
15   - Then I should see "Name: something"
16   - And I should see an image with a path of "/system/avatars/1/original/5k.png"
17   - And the file at "/system/avatars/1/original/5k.png" is the same as "test/fixtures/5k.png"
46 features/basic_integration.feature
... ... @@ -0,0 +1,46 @@
  1 +Feature: Rails integration
  2 +
  3 + Background:
  4 + Given I generate a new rails application
  5 + And I run a rails generator to generate a "User" scaffold with "name:string"
  6 + And I run a paperclip generator to add a paperclip "attachment" to the "User" model
  7 + And I run a migration
  8 + And I update my new user view to include the file upload field
  9 + And I update my user view to include the attachment
  10 +
  11 + Scenario: Filesystem integration test
  12 + Given I add this snippet to the User model:
  13 + """
  14 + has_attached_file :attachment
  15 + """
  16 + And I start the rails application
  17 + When I go to the new user page
  18 + And I fill in "Name" with "something"
  19 + And I attach the file "test/fixtures/5k.png" to "Attachment"
  20 + And I press "Submit"
  21 + Then I should see "Name: something"
  22 + And I should see an image with a path of "/system/attachments/1/original/5k.png"
  23 + And the file at "/system/attachments/1/original/5k.png" should be the same as "test/fixtures/5k.png"
  24 +
  25 + Scenario: S3 Integration test
  26 + Given I add this snippet to the User model:
  27 + """
  28 + has_attached_file :attachment,
  29 + :storage => :s3,
  30 + :path => "/:attachment/:id/:style/:filename",
  31 + :s3_credentials => Rails.root.join("config/s3.yml")
  32 + """
  33 + And I write to "config/s3.yml" with:
  34 + """
  35 + bucket: paperclip
  36 + access_key_id: access_key
  37 + secret_access_key: secret_key
  38 + """
  39 + And I start the rails application
  40 + When I go to the new user page
  41 + And I fill in "Name" with "something"
  42 + And I attach the file "test/fixtures/5k.png" to "Attachment" on S3
  43 + And I press "Submit"
  44 + Then I should see "Name: something"
  45 + And I should see an image with a path of "http://s3.amazonaws.com/paperclip/attachments/1/original/5k.png"
  46 + And the file at "http://s3.amazonaws.com/paperclip/attachments/1/original/5k.png" should be uploaded to S3
27 features/s3.feature
... ... @@ -1,27 +0,0 @@
1   -Feature: Running paperclip in a Rails app using basic S3 support
2   -
3   - Scenario: Basic utilization
4   - Given I have a rails application
5   - And I save the following as "app/models/user.rb"
6   - """
7   - class User < ActiveRecord::Base
8   - has_attached_file :avatar,
9   - :storage => :s3,
10   - :path => "/:attachment/:id/:style/:filename",
11   - :s3_credentials => Rails.root.join("config/s3.yml")
12   - end
13   - """
14   - And I validate my S3 credentials
15   - And I save the following as "config/s3.yml"
16   - """
17   - bucket: <%= ENV['PAPERCLIP_TEST_BUCKET'] || 'paperclip' %>
18   - access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
19   - secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
20   - """
21   - When I visit /users/new
22   - And I fill in "user_name" with "something"
23   - And I attach the file "test/fixtures/5k.png" to "user_avatar"
24   - And I press "Submit"
25   - Then I should see "Name: something"
26   - And I should see an image with a path of "http://s3.amazonaws.com/paperclip/avatars/1/original/5k.png"
27   - And the file at "http://s3.amazonaws.com/paperclip/avatars/1/original/5k.png" is the same as "test/fixtures/5k.png"
1  features/step_definitions/html_steps.rb
@@ -10,5 +10,6 @@
10 10 visit(web_file)
11 11 page.body
12 12 end
  13 + actual.force_encoding("UTF-8") if actual.respond_to?(:force_encoding)
13 14 actual.should == expected
14 15 end
190 features/step_definitions/rails_steps.rb
... ... @@ -1,53 +1,96 @@
1   -Given "I have a rails application" do
  1 +Given /^I generate a new rails application$/ do
2 2 steps %{
3   - Given I generate a rails application
4   - And this plugin is available
5   - And I have a "users" resource with "name:string"
  3 + When I run `bundle exec #{new_application_command} #{APP_NAME}`
  4 + And I cd to "#{APP_NAME}"
6 5 And I turn off class caching
7   - Given I save the following as "app/models/user.rb"
  6 + And I write to "Gemfile" with:
8 7 """
9   - class User < ActiveRecord::Base
10   - end
11   - """
12   - And I save the following as "config/s3.yml"
13   - """
14   - access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
15   - secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
16   - bucket: paperclip
17   - """
18   - And I save the following as "app/views/users/new.html.erb"
19   - """
20   - <% form_for @user, :html => { :multipart => true } do |f| %>
21   - <%= f.text_field :name %>
22   - <%= f.file_field :avatar %>
23   - <%= submit_tag "Submit" %>
24   - <% end %>
  8 + source "http://rubygems.org"
  9 + gem "rails", "#{framework_version}"
  10 + gem "sqlite3"
  11 + gem "capybara"
  12 + gem "gherkin"
  13 + gem "aws-s3"
25 14 """
26   - And I save the following as "app/views/users/show.html.erb"
  15 + And I configure the application to use "paperclip" from this project
  16 + And I reset Bundler environment variable
  17 + And I successfully run `bundle install --local`
  18 + }
  19 +end
  20 +
  21 +Given /^I run a rails generator to generate a "([^"]*)" scaffold with "([^"]*)"$/ do |model_name, attributes|
  22 + Given %[I successfully run `bundle exec #{generator_command} scaffold #{model_name} #{attributes}`]
  23 +end
  24 +
  25 +Given /^I run a paperclip generator to add a paperclip "([^"]*)" to the "([^"]*)" model$/ do |attachment_name, model_name|
  26 + Given %[I successfully run `bundle exec #{generator_command} paperclip #{model_name} #{attachment_name}`]
  27 +end
  28 +
  29 +Given /^I run a migration$/ do
  30 + Given %[I successfully run `bundle exec rake db:migrate`]
  31 +end
  32 +
  33 +Given /^I update my new user view to include the file upload field$/ do
  34 + if framework_version?("3")
  35 + steps %{
  36 + Given I overwrite "app/views/users/new.html.erb" with:
  37 + """
  38 + <%= form_for @user, :html => { :multipart => true } do |f| %>
  39 + <%= f.label :name %>
  40 + <%= f.text_field :name %>
  41 + <%= f.label :attachment %>
  42 + <%= f.file_field :attachment %>
  43 + <%= submit_tag "Submit" %>
  44 + <% end %>
  45 + """
  46 + }
  47 + else
  48 + steps %{
  49 + Given I overwrite "app/views/users/new.html.erb" with:
  50 + """
  51 + <% form_for @user, :html => { :multipart => true } do |f| %>
  52 + <%= f.label :name %>
  53 + <%= f.text_field :name %>
  54 + <%= f.label :attachment %>
  55 + <%= f.file_field :attachment %>
  56 + <%= submit_tag "Submit" %>
  57 + <% end %>
  58 + """
  59 + }
  60 + end
  61 +end
  62 +
  63 +Given /^I update my user view to include the attachment$/ do
  64 + steps %{
  65 + Given I overwrite "app/views/users/show.html.erb" with:
27 66 """
28 67 <p>Name: <%= @user.name %></p>
29   - <p>Avatar: <%= image_tag @user.avatar.url %></p>
  68 + <p>Attachment: <%= image_tag @user.attachment.url %></p>
30 69 """
31   - And I run "script/generate paperclip user avatar"
32   - And the rails application is prepped and running
33 70 }
34 71 end
35 72
36   -Given %r{I generate a rails application} do
37   - FileUtils.rm_rf TEMP_ROOT
38   - FileUtils.mkdir_p TEMP_ROOT
39   - Dir.chdir(TEMP_ROOT) do
40   - `rails _2.3.8_ #{APP_NAME}`
  73 +Given /^I add this snippet to the User model:$/ do |snippet|
  74 + file_name = "app/models/user.rb"
  75 + in_current_dir do
  76 + content = File.read(file_name)
  77 + File.open(file_name, 'w') { |f| f << content.sub(/end\Z/, "#{snippet}\nend") }
41 78 end
42 79 end
43 80
44   -When %r{I save the following as "([^"]*)"} do |path, string|
45   - FileUtils.mkdir_p(File.join(CUC_RAILS_ROOT, File.dirname(path)))
46   - File.open(File.join(CUC_RAILS_ROOT, path), 'w') { |file| file.write(string) }
  81 +Given /^I start the rails application$/ do
  82 + in_current_dir do
  83 + require "./config/environment"
  84 + require "capybara/rails"
  85 + end
  86 +end
  87 +
  88 +Given /^I reload my application$/ do
  89 + Rails::Application.reload!
47 90 end
48 91
49 92 When %r{I turn off class caching} do
50   - Dir.chdir(CUC_RAILS_ROOT) do
  93 + in_current_dir do
51 94 file = "config/environments/test.rb"
52 95 config = IO.read(file)
53 96 config.gsub!(%r{^\s*config.cache_classes.*$},
@@ -56,35 +99,74 @@ class User < ActiveRecord::Base
56 99 end
57 100 end
58 101
59   -When %r{the rails application is prepped and running$} do
60   - When "I reset the database"
61   - When "the rails application is running"
  102 +Given /^I update my application to use Bundler$/ do
  103 + if framework_version?("2")
  104 + boot_config_template = File.read('features/support/fixtures/boot_config.txt')
  105 + preinitializer_template = File.read('features/support/fixtures/preinitializer.txt')
  106 + gemfile_template = File.read('features/support/fixtures/gemfile.txt')
  107 + in_current_dir do
  108 + content = File.read("config/boot.rb").sub(/Rails\.boot!/, boot_config_template)
  109 + File.open("config/boot.rb", "w") { |file| file.write(content) }
  110 + File.open("config/preinitializer.rb", "w") { |file| file.write(preinitializer_template) }
  111 + File.open("Gemfile", "w") { |file| file.write(gemfile_template.sub(/RAILS_VERSION/, framework_version)) }
  112 + end
  113 + end
62 114 end
63 115
64   -When %r{I reset the database} do
65   - When %{I run "rake db:drop db:create db:migrate"}
  116 +Then /^the file at "([^"]*)" should be the same as "([^"]*)"$/ do |web_file, path|
  117 + expected = IO.read(path)
  118 + actual = if web_file.match %r{^https?://}
  119 + Net::HTTP.get(URI.parse(web_file))
  120 + else
  121 + visit(web_file)
  122 + page.source
  123 + end
  124 + actual.force_encoding("UTF-8") if actual.respond_to?(:force_encoding)
  125 + actual.should == expected
66 126 end
67 127
68   -When %r{the rails application is running} do
69   - Dir.chdir(CUC_RAILS_ROOT) do
70   - require "config/environment"
71   - require "capybara/rails"
72   - end
  128 +When /^I configure the application to use "([^\"]+)" from this project$/ do |name|
  129 + append_to_gemfile "gem '#{name}', :path => '#{PROJECT_ROOT}'"
  130 + steps %{And I run `bundle install --local`}
73 131 end
74 132
75   -When %r{this plugin is available} do
76   - $LOAD_PATH << "#{PROJECT_ROOT}/lib"
77   - require 'paperclip'
78   - When %{I save the following as "vendor/plugins/paperclip/rails/init.rb"},
79   - IO.read("#{PROJECT_ROOT}/rails/init.rb")
  133 +When /^I configure the application to use "([^\"]+)"$/ do |gem_name|
  134 + append_to_gemfile "gem '#{gem_name}'"
80 135 end
81 136
82   -When %r{I run "([^"]*)"} do |command|
83   - Dir.chdir(CUC_RAILS_ROOT) do
84   - `#{command}`
  137 +When /^I append gems from Appraisal Gemfile$/ do
  138 + File.read(ENV['BUNDLE_GEMFILE']).split(/\n/).each do |line|
  139 + if line =~ /^gem "(?!rails|appraisal)/
  140 + append_to_gemfile line.strip
  141 + end
85 142 end
86 143 end
87 144
88   -When %r{I have a "([^"]*)" resource with "([^"]*)"} do |resource, fields|
89   - When %{I run "script/generate scaffold #{resource} #{fields}"}
  145 +When /^I comment out the gem "([^"]*)" from the Gemfile$/ do |gemname|
  146 + comment_out_gem_in_gemfile gemname
90 147 end
  148 +
  149 +module FileHelpers
  150 + def append_to(path, contents)
  151 + in_current_dir do
  152 + File.open(path, "a") do |file|
  153 + file.puts
  154 + file.puts contents
  155 + end
  156 + end
  157 + end
  158 +
  159 + def append_to_gemfile(contents)
  160 + append_to('Gemfile', contents)
  161 + end
  162 +
  163 + def comment_out_gem_in_gemfile(gemname)
  164 + in_current_dir do
  165 + gemfile = File.read("Gemfile")
  166 + gemfile.sub!(/^(\s*)(gem\s*['"]#{gemname})/, "\\1# \\2")
  167 + File.open("Gemfile", 'w'){ |file| file.write(gemfile) }
  168 + end
  169 + end
  170 +end
  171 +
  172 +World(FileHelpers)
19 features/step_definitions/s3_steps.rb
... ... @@ -1,9 +1,14 @@
1   -Given /I validate my S3 credentials/ do
2   - key = ENV['AWS_ACCESS_KEY_ID']
3   - secret = ENV['AWS_SECRET_ACCESS_KEY']
4   -
5   - key.should_not be_nil
6   - secret.should_not be_nil
  1 +When /^I attach the file "([^"]*)" to "([^"]*)" on S3$/ do |file_path, field|
  2 + definition = User.attachment_definitions[field.downcase.to_sym]
  3 + path = "http://s3.amazonaws.com/paperclip#{definition[:path]}"
  4 + path.gsub!(':filename', File.basename(file_path))
  5 + path.gsub!(/:([^\/\.]+)/) do |match|
  6 + "([^\/\.]+)"
  7 + end
  8 + FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
  9 + When "I attach the file \"#{file_path}\" to \"#{field}\""
  10 +end
7 11
8   - assert_credentials(key, secret)
  12 +Then /^the file at "([^"]*)" should be uploaded to S3$/ do |url|
  13 + FakeWeb.registered_uri?(:put, url)
9 14 end
194 features/step_definitions/web_steps.rb
... ... @@ -1,21 +1,46 @@
1   -# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
2   -# It is recommended to regenerate this file in the future when you upgrade to a
3   -# newer version of cucumber-rails. Consider adding your own code to a new file
4   -# instead of editing this one. Cucumber will automatically load all features/**/*.rb
5   -# files.
  1 +# TL;DR: YOU SHOULD DELETE THIS FILE
  2 +#
  3 +# This file was generated by Cucumber-Rails and is only here to get you a head start
  4 +# These step definitions are thin wrappers around the Capybara/Webrat API that lets you
  5 +# visit pages, interact with widgets and make assertions about page content.
  6 +#
  7 +# If you use these step definitions as basis for your features you will quickly end up
  8 +# with features that are:
  9 +#
  10 +# * Hard to maintain
  11 +# * Verbose to read
  12 +#
  13 +# A much better approach is to write your own higher level step definitions, following
  14 +# the advice in the following blog posts:
  15 +#
  16 +# * http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html
  17 +# * http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/
  18 +# * http://elabs.se/blog/15-you-re-cuking-it-wrong
  19 +#
6 20
7 21
8 22 require 'uri'
9 23 require 'cgi'
10 24 require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
  25 +require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "selectors"))
11 26
12 27 module WithinHelpers
13 28 def with_scope(locator)
14   - locator ? within(locator) { yield } : yield
  29 + locator ? within(*selector_for(locator)) { yield } : yield
15 30 end
16 31 end
17 32 World(WithinHelpers)
18 33
  34 +# Single-line step scoper
  35 +When /^(.*) within (.*[^:])$/ do |step, parent|
  36 + with_scope(parent) { When step }
  37 +end
  38 +
  39 +# Multi-line step scoper
  40 +When /^(.*) within (.*[^:]):$/ do |step, parent, table_or_string|
  41 + with_scope(parent) { When "#{step}:", table_or_string }
  42 +end
  43 +
19 44 Given /^(?:|I )am on (.+)$/ do |page_name|
20 45 visit path_to(page_name)
21 46 end
@@ -24,32 +49,20 @@ def with_scope(locator)
24 49 visit path_to(page_name)
25 50 end
26 51
27   -When /^(?:|I )visit (\/.+)$/ do |page_path|
28   - visit page_path
  52 +When /^(?:|I )press "([^"]*)"$/ do |button|
  53 + click_button(button)
29 54 end
30 55
31   -When /^(?:|I )press "([^"]*)"(?: within "([^"]*)")?$/ do |button, selector|
32   - with_scope(selector) do
33   - click_button(button)
34   - end
  56 +When /^(?:|I )follow "([^"]*)"$/ do |link|
  57 + click_link(link)
35 58 end
36 59
37   -When /^(?:|I )follow "([^"]*)"(?: within "([^"]*)")?$/ do |link, selector|
38   - with_scope(selector) do
39   - click_link(link)
40   - end
  60 +When /^(?:|I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value|
  61 + fill_in(field, :with => value)
41 62 end
42 63
43   -When /^(?:|I )fill in "([^"]*)" with "([^"]*)"(?: within "([^"]*)")?$/ do |field, value, selector|
44   - with_scope(selector) do
45   - fill_in(field, :with => value)
46   - end
47   -end
48   -
49   -When /^(?:|I )fill in "([^"]*)" for "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
50   - with_scope(selector) do
51   - fill_in(field, :with => value)
52   - end
  64 +When /^(?:|I )fill in "([^"]*)" for "([^"]*)"$/ do |value, field|
  65 + fill_in(field, :with => value)
53 66 end
54 67
55 68 # Use this to fill in an entire form with data from a table. Example:
@@ -63,119 +76,92 @@ def with_scope(locator)
63 76 # TODO: Add support for checkbox, select og option
64 77 # based on naming conventions.
65 78 #
66   -When /^(?:|I )fill in the following(?: within "([^"]*)")?:$/ do |selector, fields|
67   - with_scope(selector) do
68   - fields.rows_hash.each do |name, value|
69   - When %{I fill in "#{name}" with "#{value}"}
70   - end
  79 +When /^(?:|I )fill in the following:$/ do |fields|
  80 + fields.rows_hash.each do |name, value|
  81 + When %{I fill in "#{name}" with "#{value}"}
71 82 end
72 83 end
73 84
74   -When /^(?:|I )select "([^"]*)" from "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
75   - with_scope(selector) do
76   - select(value, :from => field)
77   - end
  85 +When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field|
  86 + select(value, :from => field)
78 87 end
79 88
80   -When /^(?:|I )check "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
81   - with_scope(selector) do
82   - check(field)
83   - end
  89 +When /^(?:|I )check "([^"]*)"$/ do |field|
  90 + check(field)
84 91 end
85 92
86   -When /^(?:|I )uncheck "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
87   - with_scope(selector) do
88   - uncheck(field)
89   - end
  93 +When /^(?:|I )uncheck "([^"]*)"$/ do |field|
  94 + uncheck(field)
90 95 end
91 96
92   -When /^(?:|I )choose "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
93   - with_scope(selector) do
94   - choose(field)
95   - end
  97 +When /^(?:|I )choose "([^"]*)"$/ do |field|
  98 + choose(field)
96 99 end
97 100
98   -When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"(?: within "([^"]*)")?$/ do |path, field, selector|
99   - with_scope(selector) do
100   - attach_file(field, path)
101   - end
102   -end
103   -
104   -Then /^(?:|I )should see JSON:$/ do |expected_json|
105   - require 'json'
106   - expected = JSON.pretty_generate(JSON.parse(expected_json))
107   - actual = JSON.pretty_generate(JSON.parse(response.body))
108   - expected.should == actual
  101 +When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field|
  102 + attach_file(field, File.expand_path(path))
109 103 end
110 104
111   -Then /^(?:|I )should see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
112   - with_scope(selector) do
113   - if page.respond_to? :should
114   - page.should have_content(text)
115   - else
116   - assert page.has_content?(text)
117   - end
  105 +Then /^(?:|I )should see "([^"]*)"$/ do |text|
  106 + if page.respond_to? :should
  107 + page.should have_content(text)
  108 + else
  109 + assert page.has_content?(text)
118 110 end
119 111 end
120 112
121   -Then /^(?:|I )should see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
  113 +Then /^(?:|I )should see \/([^\/]*)\/$/ do |regexp|
122 114 regexp = Regexp.new(regexp)
123   - with_scope(selector) do
124   - if page.respond_to? :should
125   - page.should have_xpath('//*', :text => regexp)
126   - else
127   - assert page.has_xpath?('//*', :text => regexp)
128   - end
  115 +
  116 + if page.respond_to? :should
  117 + page.should have_xpath('//*', :text => regexp)
  118 + else
  119 + assert page.has_xpath?('//*', :text => regexp)
129 120 end
130 121 end
131 122
132   -Then /^(?:|I )should not see "([^"]*)"(?: within "([^"]*)")?$/ do |text, selector|
133   - with_scope(selector) do
134   - if page.respond_to? :should
135   - page.should have_no_content(text)
136   - else
137   - assert page.has_no_content?(text)
138   - end
  123 +Then /^(?:|I )should not see "([^"]*)"$/ do |text|
  124 + if page.respond_to? :should
  125 + page.should have_no_content(text)
  126 + else
  127 + assert page.has_no_content?(text)
139 128 end
140 129 end
141 130
142   -Then /^(?:|I )should not see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
  131 +Then /^(?:|I )should not see \/([^\/]*)\/$/ do |regexp|
143 132 regexp = Regexp.new(regexp)
144   - with_scope(selector) do
145   - if page.respond_to? :should
146   - page.should have_no_xpath('//*', :text => regexp)
147   - else
148   - assert page.has_no_xpath?('//*', :text => regexp)
149   - end
  133 +
  134 + if page.respond_to? :should
  135 + page.should have_no_xpath('//*', :text => regexp)
  136 + else
  137 + assert page.has_no_xpath?('//*', :text => regexp)
150 138 end
151 139 end
152 140
153   -Then /^the "([^"]*)" field(?: within "([^"]*)")? should contain "([^"]*)"$/ do |field, selector, value|
154   - with_scope(selector) do
  141 +Then /^the "([^"]*)" field(?: within (.*))? should contain "([^"]*)"$/ do |field, parent, value|
  142 + with_scope(parent) do
155 143 field = find_field(field)
156   - field_value = (field.tag_name == 'textarea') ? field.text : field.value
157   - if field_value.respond_to? :should
158   - field_value.should =~ /#{value}/
  144 + if field.value.respond_to? :should
  145 + field.value.should =~ /#{value}/
159 146 else
160   - assert_match(/#{value}/, field_value)
  147 + assert_match(/#{value}/, field.value)
161 148 end
162 149 end
163 150 end
164 151
165   -Then /^the "([^"]*)" field(?: within "([^"]*)")? should not contain "([^"]*)"$/ do |field, selector, value|
166   - with_scope(selector) do
  152 +Then /^the "([^"]*)" field(?: within (.*))? should not contain "([^"]*)"$/ do |field, parent, value|
  153 + with_scope(parent) do
167 154 field = find_field(field)
168   - field_value = (field.tag_name == 'textarea') ? field.text : field.value
169   - if field_value.respond_to? :should_not
170   - field_value.should_not =~ /#{value}/
  155 + if field.value.respond_to? :should_not
  156 + field.value.should_not =~ /#{value}/
171 157 else
172   - assert_no_match(/#{value}/, field_value)
  158 + assert_no_match(/#{value}/, field.value)
173 159 end
174 160 end
175 161 end
176 162
177   -Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should be checked$/ do |label, selector|
178   - with_scope(selector) do
  163 +Then /^the "([^"]*)" checkbox(?: within (.*))? should be checked$/ do |label, parent|
  164 + with_scope(parent) do
179 165 field_checked = find_field(label)['checked']
180 166 if field_checked.respond_to? :should
181 167 field_checked.should be_true
@@ -185,8 +171,8 @@ def with_scope(locator)
185 171 end
186 172 end
187 173
188   -Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should not be checked$/ do |label, selector|
189   - with_scope(selector) do
  174 +Then /^the "([^"]*)" checkbox(?: within (.*))? should not be checked$/ do |label, parent|
  175 + with_scope(parent) do
190 176 field_checked = find_field(label)['checked']
191 177 if field_checked.respond_to? :should
192 178 field_checked.should be_false
@@ -218,10 +204,6 @@ def with_scope(locator)
218 204 end
219 205 end
220 206
221   -Then /^I save and open the page$/ do
222   - save_and_open_page
223   -end
224   -
225 207 Then /^show me the page$/ do
226 208 save_and_open_page
227 209 end
5 features/support/env.rb
... ... @@ -1,3 +1,8 @@
  1 +require 'aruba/cucumber'
1 2 require 'capybara/cucumber'
2 3 require 'test/unit/assertions'
3 4 World(Test::Unit::Assertions)
  5 +
  6 +Before do
  7 + @aruba_timeout_seconds = 120
  8 +end
3  features/support/fakeweb.rb
... ... @@ -0,0 +1,3 @@
  1 +require 'fake_web'
  2 +
  3 +FakeWeb.allow_net_connect = false
BIN  features/support/fixtures/.boot_config.rb.swo
Binary file not shown
15 features/support/fixtures/boot_config.txt
... ... @@ -0,0 +1,15 @@
  1 +class Rails::Boot
  2 + def run
  3 + load_initializer
  4 +
  5 + Rails::Initializer.class_eval do
  6 + def load_gems
  7 + @bundler_loaded ||= Bundler.require :default, Rails.env
  8 + end
  9 + end
  10 +
  11 + Rails::Initializer.run(:set_load_path)
  12 + end
  13 +end
  14 +
  15 +Rails.boot!
5 features/support/fixtures/gemfile.txt
... ... @@ -0,0 +1,5 @@
  1 +source "http://rubygems.org"
  2 +
  3 +gem "rails", "RAILS_VERSION"
  4 +gem "rdoc"
  5 +gem "sqlite3"
20 features/support/fixtures/preinitializer.txt
... ... @@ -0,0 +1,20 @@
  1 +begin
  2 + require "rubygems"
  3 + require "bundler"
  4 +rescue LoadError
  5 + raise "Could not load the bundler gem. Install it with `gem install bundler`."
  6 +end
  7 +
  8 +if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24")
  9 + raise RuntimeError, "Your bundler version is too old for Rails 2.3." +
  10 + "Run `gem install bundler` to upgrade."
  11 +end
  12 +
  13 +begin
  14 + # Set up load paths for all bundled gems
  15 + ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__)
  16 + Bundler.setup
  17 +rescue Bundler::GemNotFound
  18 + raise RuntimeError, "Bundler couldn't find some gems." +
  19 + "Did you run `bundle install`?"
  20 +end
11 features/support/paths.rb
@@ -8,17 +8,10 @@ module NavigationHelpers
8 8 def path_to(page_name)
9 9 case page_name
10 10
11   - when /the new user page/
12   - '/users/new'
13 11 when /the home\s?page/
14 12 '/'
15   -
16   - # Add more mappings here.
17   - # Here is an example that pulls values out of the Regexp:
18   - #
19   - # when /^(.*)'s profile page$/i
20   - # user_profile_path(User.find_by_login($1))
21   -
  13 + when /the new user page/
  14 + '/users/new'
22 15 else
23 16 begin
24 17 page_name =~ /the (.*) page/
41 features/support/rails.rb
... ... @@ -1,5 +1,42 @@
1 1 PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
2   -TEMP_ROOT = File.join(PROJECT_ROOT, 'tmp').freeze
3 2 APP_NAME = 'testapp'.freeze
4   -CUC_RAILS_ROOT = File.join(TEMP_ROOT, APP_NAME).freeze
  3 +BUNDLE_ENV_VARS = %w(RUBYOPT BUNDLE_PATH BUNDLE_BIN_PATH BUNDLE_GEMFILE)
  4 +ORIGINAL_BUNDLE_VARS = Hash[ENV.select{ |key,value| BUNDLE_ENV_VARS.include?(key) }]
  5 +
5 6 ENV['RAILS_ENV'] = 'test'
  7 +
  8 +Before do
  9 + ENV['BUNDLE_GEMFILE'] = File.join(Dir.pwd, ENV['BUNDLE_GEMFILE']) unless ENV['BUNDLE_GEMFILE'].start_with?(Dir.pwd)
  10 + @framework_version = nil
  11 +end
  12 +
  13 +After do
  14 + ORIGINAL_BUNDLE_VARS.each_pair do |key, value|
  15 + ENV[key] = value
  16 + end
  17 +end
  18 +
  19 +When /^I reset Bundler environment variable$/ do
  20 + BUNDLE_ENV_VARS.each do |key|
  21 + ENV[key] = nil
  22 + end
  23 +end
  24 +
  25 +module RailsCommandHelpers
  26 + def framework_version?(version_string)
  27 + framework_version =~ /^#{version_string}/
  28 + end
  29 +
  30 + def framework_version
  31 + @framework_version ||= `rails -v`[/^Rails (.+)$/, 1]
  32 + end
  33 +
  34 + def new_application_command
  35 + framework_version?("3") ? "rails new" : "rails"
  36 + end
  37 +
  38 + def generator_command
  39 + framework_version?("3") ? "script/rails generate" : "script/generate"
  40 + end
  41 +end
  42 +World(RailsCommandHelpers)
22 features/support/s3.rb
... ... @@ -1,22 +0,0 @@
1   -module AWSS3Methods
2   - def load_s3
3   - begin
4   - require 'aws-sdk'
5   - rescue LoadError => e
6   - fail "You do not have aws-sdk installed."
7   - end
8   - end
9   -
10   - def assert_credentials(key, secret)
11   - load_s3
12   - begin
13   - AWS::S3.new(:access_key_id => key,
14   - :secret_access_key => secret).buckets.to_a
15   - rescue AWS::Errors::Base => e
16   - fail "Could not connect using AWS credentials in AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. " +
17   - "Please make sure these are set in your environment."
18   - end
19   - end
20   -end
21   -
22   -World(AWSS3Methods)
19 features/support/selectors.rb
... ... @@ -0,0 +1,19 @@
  1 +module HtmlSelectorsHelpers
  2 + # Maps a name to a selector. Used primarily by the
  3 + #
  4