Skip to content
This repository
Browse code

Merge remote branch 'upstream/master'

Conflicts:
	lib/paperclip/upfile.rb
	paperclip.gemspec
  • Loading branch information...
commit 237597eb3a920ebef8ad7d2ab0fb5e4e0ccd4d24 2 parents 3eb7b90 + cbfaca4
Mike Mondragon authored August 23, 2011

Showing 77 changed files with 4,299 additions and 1,234 deletions. Show diff stats Hide diff stats

  1. 6  .gitignore
  2. 11  .travis.yml
  3. 11  Appraisals
  4. 38  CONTRIBUTING.md
  5. 21  Gemfile
  6. 100  Gemfile.lock
  7. 270  README.md
  8. 174  README.rdoc
  9. 59  Rakefile
  10. 17  features/basic.feature
  11. 27  features/s3.feature
  12. 14  features/step_definitions/html_steps.rb
  13. 90  features/step_definitions/rails_steps.rb
  14. 9  features/step_definitions/s3_steps.rb
  15. 227  features/step_definitions/web_steps.rb
  16. 3  features/support/env.rb
  17. 35  features/support/paths.rb
  18. 5  features/support/rails.rb
  19. 25  features/support/s3.rb
  20. 20  gemfiles/rails2.gemfile
  21. 121  gemfiles/rails2.gemfile.lock
  22. 20  gemfiles/rails3.gemfile
  23. 163  gemfiles/rails3.gemfile.lock
  24. 20  gemfiles/rails3_1.gemfile
  25. 172  gemfiles/rails3_1.gemfile.lock
  26. 4  generators/paperclip/USAGE
  27. 16  generators/paperclip/paperclip_generator.rb
  28. 3  init.rb
  29. 8  lib/generators/paperclip/USAGE
  30. 33  lib/generators/paperclip/paperclip_generator.rb
  31. 19  lib/generators/paperclip/templates/paperclip_migration.rb.erb
  32. 253  lib/paperclip.rb
  33. 212  lib/paperclip/attachment.rb
  34. 33  lib/paperclip/callback_compatability.rb
  35. 61  lib/paperclip/callback_compatibility.rb
  36. 19  lib/paperclip/geometry.rb
  37. 78  lib/paperclip/interpolations.rb
  38. 38  lib/paperclip/iostream.rb
  39. 29  lib/paperclip/matchers.rb
  40. 8  lib/paperclip/matchers/have_attached_file_matcher.rb
  41. 44  lib/paperclip/matchers/validate_attachment_content_type_matcher.rb
  42. 12  lib/paperclip/matchers/validate_attachment_presence_matcher.rb
  43. 14  lib/paperclip/matchers/validate_attachment_size_matcher.rb
  44. 21  lib/paperclip/processor.rb
  45. 24  lib/paperclip/railtie.rb
  46. 250  lib/paperclip/storage.rb
  47. 78  lib/paperclip/storage/filesystem.rb
  48. 133  lib/paperclip/storage/fog.rb
  49. 230  lib/paperclip/storage/s3.rb
  50. 33  lib/paperclip/style.rb
  51. 57  lib/paperclip/thumbnail.rb
  52. 36  lib/paperclip/upfile.rb
  53. 3  lib/paperclip/version.rb
  54. 81  lib/tasks/paperclip.rake
  55. 78  paperclip.gemspec
  56. 2  rails/init.rb
  57. 9  shoulda_macros/paperclip.rb
  58. 79  tasks/paperclip_tasks.rake
  59. 391  test/attachment_test.rb
  60. BIN  test/fixtures/animated.gif
  61. BIN  test/fixtures/uppercase.PNG
  62. 131  test/fog_test.rb
  63. 33  test/geometry_test.rb
  64. 101  test/helper.rb
  65. 177  test/integration_test.rb
  66. 84  test/interpolations_test.rb
  67. 21  test/iostream_test.rb
  68. 17  test/matchers/have_attached_file_matcher_test.rb
  69. 74  test/matchers/validate_attachment_content_type_matcher_test.rb
  70. 15  test/matchers/validate_attachment_presence_matcher_test.rb
  71. 36  test/matchers/validate_attachment_size_matcher_test.rb
  72. 171  test/paperclip_test.rb
  73. 2  test/processor_test.rb
  74. 371  test/storage_test.rb
  75. 87  test/style_test.rb
  76. 151  test/thumbnail_test.rb
  77. 15  test/upfile_test.rb
6  .gitignore
@@ -3,3 +3,9 @@
3 3
 tmp
4 4
 test/s3.yml
5 5
 public
  6
+paperclip*.gem
  7
+capybara*.html
  8
+*.rbc
  9
+.bundle
  10
+*SPIKE*
  11
+.rvmrc
11  .travis.yml
... ...
@@ -0,0 +1,11 @@
  1
+rvm:
  2
+  - 1.8.7
  3
+  - 1.9.2
  4
+  - ree
  5
+  - rbx-2.0
  6
+  
  7
+script: "bundle exec rake clean test"
  8
+gemfile:
  9
+  - gemfiles/rails2.gemfile
  10
+  - gemfiles/rails3.gemfile
  11
+  - gemfiles/rails3_1.gemfile
11  Appraisals
... ...
@@ -0,0 +1,11 @@
  1
+appraise "rails2" do
  2
+  gem "rails", "~> 2.3.12"
  3
+end
  4
+
  5
+appraise "rails3" do
  6
+  gem "rails", "~> 3.0.9"
  7
+end
  8
+
  9
+appraise "rails3_1" do
  10
+  gem "rails", "~> 3.1.0.rc5"
  11
+end
38  CONTRIBUTING.md
Source Rendered
... ...
@@ -0,0 +1,38 @@
  1
+We love pull requests. Here's a quick guide:
  2
+
  3
+1. Fork the repo.
  4
+
  5
+2. Run the tests. We only take pull requests with passing tests, and it's great
  6
+to know that you have a clean slate: `bundle && rake`
  7
+
  8
+3. Add a test for your change. Only refactoring and documentation changes
  9
+require no new tests. If you are adding functionality or fixing a bug, we need
  10
+a test!
  11
+
  12
+4. Make the test pass.
  13
+
  14
+5. Push to your fork and submit a pull request.
  15
+
  16
+
  17
+At this point you're waiting on us. We like to at least comment on, if not
  18
+accept, pull requests within three business days (and, typically, one business
  19
+day). We may suggest some changes or improvements or alternatives.
  20
+
  21
+Some things that will increase the chance that your pull request is accepted,
  22
+taken straight from the Ruby on Rails guide:
  23
+
  24
+* Use Rails idioms and helpers
  25
+* Include tests that fail without your code, and pass with it
  26
+* Update the documentation, the surrounding one, examples elsewhere, guides,
  27
+  whatever is affected by your contribution
  28
+
  29
+Syntax:
  30
+
  31
+* Two spaces, no tabs.
  32
+* No trailing whitespace. Blank lines should not have any space.
  33
+* Prefer &&/|| over and/or.
  34
+* MyClass.my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
  35
+* a = b and not a=b.
  36
+* Follow the conventions you see used in the source already.
  37
+
  38
+And in case we didn't emphasize it enough: we love tests!
21  Gemfile
... ...
@@ -0,0 +1,21 @@
  1
+source "http://rubygems.org"
  2
+
  3
+gem "activerecord", :require => "active_record"
  4
+gem "appraisal"
  5
+gem "aws-s3", :require => "aws/s3"
  6
+gem "bundler"
  7
+gem "cocaine", "~>0.2"
  8
+gem "fog"
  9
+gem "jruby-openssl", :platform => :jruby
  10
+gem "mime-types"
  11
+gem "mocha"
  12
+gem "rake"
  13
+gem "rdoc", :require => false
  14
+gem "shoulda"
  15
+gem "sqlite3", "~>1.3.4"
  16
+
  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
100  Gemfile.lock
... ...
@@ -0,0 +1,100 @@
  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-s3 (0.6.2)
  16
+      builder
  17
+      mime-types
  18
+      xml-simple
  19
+    bouncy-castle-java (1.5.0146.1)
  20
+    builder (3.0.0)
  21
+    childprocess (0.1.9)
  22
+      ffi (~> 1.0.6)
  23
+    cocaine (0.2.0)
  24
+    cucumber (0.10.5)
  25
+      builder (>= 2.1.2)
  26
+      diff-lcs (>= 1.1.2)
  27
+      gherkin (~> 2.4.0)
  28
+      json (>= 1.4.6)
  29
+      term-ansicolor (>= 1.0.5)
  30
+    diff-lcs (1.1.2)
  31
+    excon (0.6.3)
  32
+    ffi (1.0.9)
  33
+    ffi (1.0.9-java)
  34
+    fog (0.8.2)
  35
+      builder
  36
+      excon (~> 0.6.1)
  37
+      formatador (>= 0.1.3)
  38
+      json
  39
+      mime-types
  40
+      net-ssh (>= 2.1.3)
  41
+      nokogiri (>= 1.4.4)
  42
+      ruby-hmac
  43
+    formatador (0.1.4)
  44
+    gherkin (2.4.5)
  45
+      json (>= 1.4.6)
  46
+    gherkin (2.4.5-java)
  47
+      json (>= 1.4.6)
  48
+    hike (1.2.0)
  49
+    jruby-openssl (0.7.4)
  50
+      bouncy-castle-java
  51
+    json (1.5.3)
  52
+    json (1.5.3-java)
  53
+    mime-types (1.16)
  54
+    mocha (0.9.12)
  55
+    net-ssh (2.1.4)
  56
+    nokogiri (1.4.4)
  57
+    nokogiri (1.4.4-java)
  58
+      weakling (>= 0.0.3)
  59
+    rack (1.3.2)
  60
+    rake (0.9.2)
  61
+    rdoc (3.8)
  62
+    rspec (2.6.0)
  63
+      rspec-core (~> 2.6.0)
  64
+      rspec-expectations (~> 2.6.0)
  65
+      rspec-mocks (~> 2.6.0)
  66
+    rspec-core (2.6.4)
  67
+    rspec-expectations (2.6.0)
  68
+      diff-lcs (~> 1.1.2)
  69
+    rspec-mocks (2.6.0)
  70
+    ruby-hmac (0.4.0)
  71
+    shoulda (2.11.3)
  72
+    sprockets (2.0.0.beta.13)
  73
+      hike (~> 1.2)
  74
+      rack (~> 1.0)
  75
+      tilt (~> 1.1, != 1.3.0)
  76
+    sqlite3 (1.3.4)
  77
+    term-ansicolor (1.0.5)
  78
+    tilt (1.3.2)
  79
+    weakling (0.0.4-java)
  80
+    xml-simple (1.0.16)
  81
+
  82
+PLATFORMS
  83
+  java
  84
+  ruby
  85
+
  86
+DEPENDENCIES
  87
+  activerecord
  88
+  appraisal
  89
+  aws-s3
  90
+  bundler
  91
+  cocaine (~> 0.2)
  92
+  fog
  93
+  jruby-openssl
  94
+  mime-types
  95
+  mocha
  96
+  rake
  97
+  rdoc
  98
+  shoulda
  99
+  sprockets (~> 2.0.0.beta.13)
  100
+  sqlite3 (~> 1.3.4)
270  README.md
Source Rendered
... ...
@@ -0,0 +1,270 @@
  1
+# Paperclip [![Build Status](https://secure.travis-ci.org/thoughtbot/paperclip.png?branch=master)](http://travis-ci.org/thoughtbot/paperclip)
  2
+
  3
+Paperclip is intended as an easy file attachment library for ActiveRecord. The
  4
+intent behind it was to keep setup as easy as possible and to treat files as
  5
+much like other attributes as possible. This means they aren't saved to their
  6
+final locations on disk, nor are they deleted if set to nil, until
  7
+ActiveRecord::Base#save is called. It manages validations based on size and
  8
+presence, if required. It can transform its assigned image into thumbnails if
  9
+needed, and the prerequisites are as simple as installing ImageMagick (which,
  10
+for most modern Unix-based systems, is as easy as installing the right
  11
+packages). Attached files are saved to the filesystem and referenced in the
  12
+browser by an easily understandable specification, which has sensible and
  13
+useful defaults.
  14
+
  15
+See the documentation for `has_attached_file` in Paperclip::ClassMethods for
  16
+more detailed options.
  17
+
  18
+The complete [RDoc](http://rdoc.info/gems/paperclip) is online.
  19
+
  20
+Requirements
  21
+------------
  22
+
  23
+ImageMagick must be installed and Paperclip must have access to it. To ensure
  24
+that it does, on your command line, run `which convert` (one of the ImageMagick
  25
+utilities). This will give you the path where that utility is installed. For
  26
+example, it might return `/usr/local/bin/convert`.
  27
+
  28
+Then, in your environment config file, let Paperclip know to look there by adding that 
  29
+directory to its path.
  30
+
  31
+In development mode, you might add this line to `config/environments/development.rb)`:
  32
+
  33
+    Paperclip.options[:command_path] = "/usr/local/bin/"
  34
+
  35
+If you're on Mac OSX, you'll want to run the following with Homebrew:
  36
+
  37
+    brew install imagemagick
  38
+
  39
+If you are dealing with pdf uploads or running the test suite, also run:
  40
+
  41
+    brew install gs
  42
+
  43
+Installation
  44
+------------
  45
+
  46
+Paperclip is distributed as a gem, which is how it should be used in your app. It's
  47
+technically still installable as a plugin, but that's discouraged, as Rails plays
  48
+well with gems.
  49
+
  50
+Include the gem in your Gemfile:
  51
+
  52
+    gem "paperclip", "~> 2.3"
  53
+
  54
+Or, if you don't use Bundler (though you probably should, even in Rails 2), with config.gem
  55
+
  56
+    # In config/environment.rb
  57
+    ...
  58
+    Rails::Initializer.run do |config|
  59
+      ...
  60
+      config.gem "paperclip", :version => "~> 2.3"
  61
+      ...
  62
+    end
  63
+
  64
+Quick Start
  65
+-----------
  66
+
  67
+In your model:
  68
+
  69
+    class User < ActiveRecord::Base
  70
+      has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
  71
+    end
  72
+
  73
+In your migrations:
  74
+
  75
+    class AddAvatarColumnsToUser < ActiveRecord::Migration
  76
+      def self.up
  77
+        add_column :users, :avatar_file_name,    :string
  78
+        add_column :users, :avatar_content_type, :string
  79
+        add_column :users, :avatar_file_size,    :integer
  80
+        add_column :users, :avatar_updated_at,   :datetime
  81
+      end
  82
+
  83
+      def self.down
  84
+        remove_column :users, :avatar_file_name
  85
+        remove_column :users, :avatar_content_type
  86
+        remove_column :users, :avatar_file_size
  87
+        remove_column :users, :avatar_updated_at
  88
+      end
  89
+    end
  90
+
  91
+In your edit and new views:
  92
+
  93
+    <% form_for :user, @user, :url => user_path, :html => { :multipart => true } do |form| %>
  94
+      <%= form.file_field :avatar %>
  95
+    <% end %>
  96
+
  97
+In your controller:
  98
+
  99
+    def create
  100
+      @user = User.create( params[:user] )
  101
+    end
  102
+
  103
+In your show view:
  104
+
  105
+    <%= image_tag @user.avatar.url %>
  106
+    <%= image_tag @user.avatar.url(:medium) %>
  107
+    <%= image_tag @user.avatar.url(:thumb) %>
  108
+
  109
+To detach a file, simply set the attribute to `nil`:
  110
+
  111
+    @user.avatar = nil
  112
+    @user.save
  113
+
  114
+Usage
  115
+-----
  116
+
  117
+The basics of paperclip are quite simple: Declare that your model has an
  118
+attachment with the has_attached_file method, and give it a name. Paperclip
  119
+will wrap up up to four attributes (all prefixed with that attachment's name,
  120
+so you can have multiple attachments per model if you wish) and give them a
  121
+friendly front end. The attributes are `<attachment>_file_name`,
  122
+`<attachment>_file_size`, `<attachment>_content_type`, and `<attachment>_updated_at`.
  123
+Only `<attachment>_file_name` is required for paperclip to operate. More
  124
+information about the options to has_attached_file is available in the
  125
+documentation of Paperclip::ClassMethods.
  126
+
  127
+Attachments can be validated with Paperclip's validation methods,
  128
+validates_attachment_presence, validates_attachment_content_type, and
  129
+validates_attachment_size.
  130
+
  131
+Storage
  132
+-------
  133
+
  134
+The files that are assigned as attachments are, by default, placed in the
  135
+directory specified by the :path option to has_attached_file. By default, this
  136
+location is ":rails_root/public/system/:attachment/:id/:style/:filename". This
  137
+location was chosen because on standard Capistrano deployments, the
  138
+public/system directory is symlinked to the app's shared directory, meaning it
  139
+will survive between deployments. For example, using that :path, you may have a
  140
+file at
  141
+
  142
+    /data/myapp/releases/20081229172410/public/system/avatars/13/small/my_pic.png
  143
+
  144
+_NOTE: This is a change from previous versions of Paperclip, but is overall a
  145
+safer choice for the default file store._
  146
+
  147
+You may also choose to store your files using Amazon's S3 service. You can find
  148
+more information about S3 storage at the description for
  149
+Paperclip::Storage::S3.
  150
+
  151
+Files on the local filesystem (and in the Rails app's public directory) will be
  152
+available to the internet at large. If you require access control, it's
  153
+possible to place your files in a different location. You will need to change
  154
+both the :path and :url options in order to make sure the files are unavailable
  155
+to the public. Both :path and :url allow the same set of interpolated
  156
+variables.
  157
+
  158
+Post Processing
  159
+---------------
  160
+
  161
+Paperclip supports an extensible selection of post-processors. When you define
  162
+a set of styles for an attachment, by default it is expected that those
  163
+"styles" are actually "thumbnails". However, you can do much more than just
  164
+thumbnail images. By defining a subclass of Paperclip::Processor, you can
  165
+perform any processing you want on the files that are attached. Any file in
  166
+your Rails app's lib/paperclip_processors directory is automatically loaded by
  167
+paperclip, allowing you to easily define custom processors. You can specify a
  168
+processor with the :processors option to has_attached_file:
  169
+
  170
+    has_attached_file :scan, :styles => { :text => { :quality => :better } },
  171
+                             :processors => [:ocr]
  172
+
  173
+This would load the hypothetical class Paperclip::Ocr, which would have the
  174
+hash "{ :quality => :better }" passed to it along with the uploaded file. For
  175
+more information about defining processors, see Paperclip::Processor.
  176
+
  177
+The default processor is Paperclip::Thumbnail. For backwards compatability
  178
+reasons, you can pass a single geometry string or an array containing a
  179
+geometry and a format, which the file will be converted to, like so:
  180
+
  181
+    has_attached_file :avatar, :styles => { :thumb => ["32x32#", :png] }
  182
+
  183
+This will convert the "thumb" style to a 32x32 square in png format, regardless
  184
+of what was uploaded. If the format is not specified, it is kept the same (i.e.
  185
+jpgs will remain jpgs).
  186
+
  187
+Multiple processors can be specified, and they will be invoked in the order
  188
+they are defined in the :processors array. Each successive processor will
  189
+be given the result of the previous processor's execution. All processors will
  190
+receive the same parameters, which are what you define in the :styles hash.
  191
+For example, assuming we had this definition:
  192
+
  193
+    has_attached_file :scan, :styles => { :text => { :quality => :better } },
  194
+                             :processors => [:rotator, :ocr]
  195
+
  196
+then both the :rotator processor and the :ocr processor would receive the
  197
+options "{ :quality => :better }". This parameter may not mean anything to one
  198
+or more or the processors, and they are expected to ignore it.
  199
+
  200
+_NOTE: Because processors operate by turning the original attachment into the
  201
+styles, no processors will be run if there are no styles defined._
  202
+
  203
+If you're interested in caching your thumbnail's width, height and size in the
  204
+database, take a look at the [paperclip-meta](https://github.com/y8/paperclip-meta) gem.
  205
+
  206
+Also, if you're interesting to generate the thumbnail on-the-fly, you might want
  207
+to look into the [attachment_on_the_fly](https://github.com/drpentode/Attachment-on-the-Fly) gem.
  208
+
  209
+Events
  210
+------
  211
+
  212
+Before and after the Post Processing step, Paperclip calls back to the model
  213
+with a few callbacks, allowing the model to change or cancel the processing
  214
+step. The callbacks are `before_post_process` and `after_post_process` (which
  215
+are called before and after the processing of each attachment), and the
  216
+attachment-specific `before_<attachment>_post_process` and
  217
+`after_<attachment>_post_process`. The callbacks are intended to be as close to
  218
+normal ActiveRecord callbacks as possible, so if you return false (specifically
  219
+- returning nil is not the same) in a before_ filter, the post processing step
  220
+will halt. Returning false in an after_ filter will not halt anything, but you
  221
+can access the model and the attachment if necessary.
  222
+
  223
+_NOTE: Post processing will not even *start* if the attachment is not valid
  224
+according to the validations. Your callbacks and processors will *only* be
  225
+called with valid attachments._
  226
+
  227
+URI Obfuscation
  228
+---------------
  229
+
  230
+Paperclip has an interpolation called `:hash` for obfuscating filenames of publicly-available files. For more on this feature read author's own explanation.
  231
+
  232
+[https://github.com/thoughtbot/paperclip/pull/416](https://github.com/thoughtbot/paperclip/pull/416)
  233
+
  234
+Testing
  235
+-------
  236
+
  237
+Paperclip provides rspec-compatible matchers for testing attachments. See the
  238
+documentation on [Paperclip::Shoulda::Matchers](http://rubydoc.info/gems/paperclip/Paperclip/Shoulda/Matchers)
  239
+for more information.
  240
+
  241
+Contributing
  242
+------------
  243
+
  244
+If you'd like to contribute a feature or bugfix: Thanks! To make sure your
  245
+fix/feature has a high chance of being included, please read the following
  246
+guidelines:
  247
+
  248
+1. Ask on the mailing list[http://groups.google.com/group/paperclip-plugin], or 
  249
+   post a new GitHub Issue[http://github.com/thoughtbot/paperclip/issues].
  250
+2. Make sure there are tests! We will not accept any patch that is not tested.
  251
+   It's a rare time when explicit tests aren't needed. If you have questions
  252
+   about writing tests for paperclip, please ask the mailing list.
  253
+
  254
+Please see CONTRIBUTING.md for details.
  255
+
  256
+Credits
  257
+-------
  258
+
  259
+![thoughtbot](http://thoughtbot.com/images/tm/logo.png)
  260
+
  261
+Paperclip is maintained and funded by [thoughtbot, inc](http://thoughtbot.com/community)
  262
+
  263
+Thank you to all [the contributors](https://github.com/thoughtbot/paperclip/contributors)!
  264
+
  265
+The names and logos for thoughtbot are trademarks of thoughtbot, inc.
  266
+
  267
+License
  268
+-------
  269
+
  270
+Paperclip is Copyright © 2008-2011 thoughtbot. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
174  README.rdoc
Source Rendered
... ...
@@ -1,174 +0,0 @@
1  
-=Paperclip
2  
-
3  
-Paperclip is intended as an easy file attachment library for ActiveRecord. The
4  
-intent behind it was to keep setup as easy as possible and to treat files as
5  
-much like other attributes as possible. This means they aren't saved to their
6  
-final locations on disk, nor are they deleted if set to nil, until
7  
-ActiveRecord::Base#save is called. It manages validations based on size and
8  
-presence, if required. It can transform its assigned image into thumbnails if
9  
-needed, and the prerequisites are as simple as installing ImageMagick (which,
10  
-for most modern Unix-based systems, is as easy as installing the right
11  
-packages). Attached files are saved to the filesystem and referenced in the
12  
-browser by an easily understandable specification, which has sensible and
13  
-useful defaults.
14  
-
15  
-See the documentation for +has_attached_file+ in Paperclip::ClassMethods for
16  
-more detailed options.
17  
-
18  
-==Quick Start
19  
-
20  
-In your model:
21  
-
22  
-  class User < ActiveRecord::Base
23  
-    has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
24  
-  end
25  
-
26  
-In your migrations:
27  
-
28  
-  class AddAvatarColumnsToUser < ActiveRecord::Migration
29  
-    def self.up
30  
-      add_column :users, :avatar_file_name,    :string
31  
-      add_column :users, :avatar_content_type, :string
32  
-      add_column :users, :avatar_file_size,    :integer
33  
-      add_column :users, :avatar_updated_at,   :datetime
34  
-    end
35  
-
36  
-    def self.down
37  
-      remove_column :users, :avatar_file_name
38  
-      remove_column :users, :avatar_content_type
39  
-      remove_column :users, :avatar_file_size
40  
-      remove_column :users, :avatar_updated_at
41  
-    end
42  
-  end
43  
-
44  
-In your edit and new views:
45  
-
46  
-  <% form_for :user, @user, :url => user_path, :html => { :multipart => true } do |form| %>
47  
-    <%= form.file_field :avatar %>
48  
-  <% end %>
49  
-
50  
-In your controller:
51  
-
52  
-  def create
53  
-    @user = User.create( params[:user] )
54  
-  end
55  
-
56  
-In your show view:
57  
-
58  
-  <%= image_tag @user.avatar.url %>
59  
-  <%= image_tag @user.avatar.url(:medium) %>
60  
-  <%= image_tag @user.avatar.url(:thumb) %>
61  
-
62  
-==Usage
63  
-
64  
-The basics of paperclip are quite simple: Declare that your model has an
65  
-attachment with the has_attached_file method, and give it a name. Paperclip
66  
-will wrap up up to four attributes (all prefixed with that attachment's name,
67  
-so you can have multiple attachments per model if you wish) and give the a
68  
-friendly front end. The attributes are <attachment>_file_name,
69  
-<attachment>_file_size, <attachment>_content_type, and <attachment>_updated_at.
70  
-Only <attachment>_file_name is required for paperclip to operate. More
71  
-information about the options to has_attached_file is available in the
72  
-documentation of Paperclip::ClassMethods.
73  
-
74  
-Attachments can be validated with Paperclip's validation methods,
75  
-validates_attachment_presence, validates_attachment_content_type, and
76  
-validates_attachment_size.
77  
-
78  
-==Storage
79  
-
80  
-The files that are assigned as attachments are, by default, placed in the
81  
-directory specified by the :path option to has_attached_file. By default, this
82  
-location is ":rails_root/public/system/:attachment/:id/:style/:filename". This
83  
-location was chosen because on standard Capistrano deployments, the
84  
-public/system directory is symlinked to the app's shared directory, meaning it
85  
-will survive between deployments. For example, using that :path, you may have a
86  
-file at
87  
-
88  
-  /data/myapp/releases/20081229172410/public/system/avatars/13/small/my_pic.png
89  
-
90  
-NOTE: This is a change from previous versions of Paperclip, but is overall a
91  
-safer choice for the default file store.
92  
-
93  
-You may also choose to store your files using Amazon's S3 service. You can find
94  
-more information about S3 storage at the description for
95  
-Paperclip::Storage::S3.
96  
-
97  
-Files on the local filesystem (and in the Rails app's public directory) will be
98  
-available to the internet at large. If you require access control, it's
99  
-possible to place your files in a different location. You will need to change
100  
-both the :path and :url options in order to make sure the files are unavailable
101  
-to the public. Both :path and :url allow the same set of interpolated
102  
-variables.
103  
-
104  
-==Post Processing
105  
-
106  
-Paperclip supports an extensible selection of post-processors. When you define
107  
-a set of styles for an attachment, by default it is expected that those
108  
-"styles" are actually "thumbnails". However, you can do much more than just
109  
-thumbnail images. By defining a subclass of Paperclip::Processor, you can
110  
-perform any processing you want on the files that are attached. Any file in
111  
-your Rails app's lib/paperclip_processors directory is automatically loaded by
112  
-paperclip, allowing you to easily define custom processors. You can specify a
113  
-processor with the :processors option to has_attached_file:
114  
-
115  
-  has_attached_file :scan, :styles => { :text => { :quality => :better } },
116  
-                           :processors => [:ocr]
117  
-
118  
-This would load the hypothetical class Paperclip::Ocr, which would have the
119  
-hash "{ :quality => :better }" passed to it along with the uploaded file. For
120  
-more information about defining processors, see Paperclip::Processor.
121  
-
122  
-The default processor is Paperclip::Thumbnail. For backwards compatability
123  
-reasons, you can pass a single geometry string or an array containing a
124  
-geometry and a format, which the file will be converted to, like so:
125  
-
126  
-  has_attached_file :avatar, :styles => { :thumb => ["32x32#", :png] }
127  
-
128  
-This will convert the "thumb" style to a 32x32 square in png format, regardless
129  
-of what was uploaded. If the format is not specified, it is kept the same (i.e.
130  
-jpgs will remain jpgs).
131  
-
132  
-Multiple processors can be specified, and they will be invoked in the order
133  
-they are defined in the :processors array. Each successive processor will
134  
-be given the result of the previous processor's execution. All processors will
135  
-receive the same parameters, which are what you define in the :styles hash.
136  
-For example, assuming we had this definition:
137  
-
138  
-  has_attached_file :scan, :styles => { :text => { :quality => :better } },
139  
-                           :processors => [:rotator, :ocr]
140  
-
141  
-then both the :rotator processor and the :ocr processor would receive the 
142  
-options "{ :quality => :better }". This parameter may not mean anything to one
143  
-or more or the processors, and they are expected to ignore it.
144  
-
145  
-NOTE: Because processors operate by turning the original attachment into the
146  
-styles, no processors will be run if there are no styles defined.
147  
-
148  
-==Events
149  
-
150  
-Before and after the Post Processing step, Paperclip calls back to the model
151  
-with a few callbacks, allowing the model to change or cancel the processing
152  
-step. The callbacks are "before_post_process" and "after_post_process" (which
153  
-are called before and after the processing of each attachment), and the
154  
-attachment-specific "before_<attachment>_post_process" and
155  
-"after_<attachment>_post_process". The callbacks are intended to be as close to
156  
-normal ActiveRecord callbacks as possible, so if you return false (specifically
157  
-- returning nil is not the same) in a before_ filter, the post processing step
158  
-will halt. Returning false in an after_ filter will not halt anything, but you
159  
-can access the model and the attachment if necessary.
160  
-
161  
-NOTE: Post processing will not even *start* if the attachment is not valid
162  
-according to the validations. Your callbacks and processors will *only* be
163  
-called with valid attachments.
164  
-
165  
-==Contributing
166  
-
167  
-If you'd like to contribute a feature or bugfix: Thanks! To make sure your
168  
-fix/feature has a high chance of being included, please read the following
169  
-guidelines:
170  
-
171  
-1. Ask on the mailing list, or post a new GitHub Issue.
172  
-2. Make sure there are tests! We will not accept any patch that is not tested.
173  
-   It's a rare time when explicit tests aren't needed. If you have questions
174  
-   about writing tests for paperclip, please ask the mailing list.
59  Rakefile
... ...
@@ -1,12 +1,21 @@
  1
+require 'rubygems'
  2
+require 'appraisal'
  3
+require 'bundler/setup'
  4
+
1 5
 require 'rake'
2 6
 require 'rake/testtask'
3  
-require 'rake/rdoctask'
  7
+require 'rdoc/task'
4 8
 
5 9
 $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
6 10
 require 'paperclip'
7 11
 
8 12
 desc 'Default: run unit tests.'
9  
-task :default => [:clean, :test]
  13
+task :default => [:clean, 'appraisal:install', :all]
  14
+
  15
+desc 'Test the paperclip plugin under all supported Rails versions.'
  16
+task :all do |t|
  17
+  exec('rake appraisal test')
  18
+end
10 19
 
11 20
 desc 'Test the paperclip plugin.'
12 21
 Rake::TestTask.new(:test) do |t|
@@ -22,7 +31,7 @@ task :shell do |t|
22 31
 end
23 32
 
24 33
 desc 'Generate documentation for the paperclip plugin.'
25  
-Rake::RDocTask.new(:rdoc) do |rdoc|
  34
+RDoc::Task.new(:rdoc) do |rdoc|
26 35
   rdoc.rdoc_dir = 'doc'
27 36
   rdoc.title    = 'Paperclip'
28 37
   rdoc.options << '--line-numbers' << '--inline-source'
@@ -40,47 +49,15 @@ task :clean do |t|
40 49
   FileUtils.rm_rf "doc"
41 50
   FileUtils.rm_rf "tmp"
42 51
   FileUtils.rm_rf "pkg"
  52
+  FileUtils.rm_rf "public"
43 53
   FileUtils.rm "test/debug.log" rescue nil
44 54
   FileUtils.rm "test/paperclip.db" rescue nil
45 55
   Dir.glob("paperclip-*.gem").each{|f| FileUtils.rm f }
46 56
 end
47 57
 
48  
-include_file_globs = ["README*",
49  
-                      "LICENSE",
50  
-                      "Rakefile",
51  
-                      "init.rb",
52  
-                      "{generators,lib,tasks,test,shoulda_macros}/**/*"]
53  
-exclude_file_globs = ["test/s3.yml",
54  
-                      "test/debug.log",
55  
-                      "test/paperclip.db",
56  
-                      "test/doc",
57  
-                      "test/doc/*",
58  
-                      "test/pkg",
59  
-                      "test/pkg/*",
60  
-                      "test/tmp",
61  
-                      "test/tmp/*"]
62  
-spec = Gem::Specification.new do |s| 
63  
-  s.name              = "paperclip"
64  
-  s.version           = Paperclip::VERSION
65  
-  s.author            = "Jon Yurek"
66  
-  s.email             = "jyurek@thoughtbot.com"
67  
-  s.homepage          = "http://www.thoughtbot.com/projects/paperclip"
68  
-  s.platform          = Gem::Platform::RUBY
69  
-  s.summary           = "File attachments as attributes for ActiveRecord"
70  
-  s.files             = FileList[include_file_globs].to_a - FileList[exclude_file_globs].to_a
71  
-  s.require_path      = "lib"
72  
-  s.test_files        = FileList["test/**/test_*.rb"].to_a
73  
-  s.rubyforge_project = "paperclip"
74  
-  s.has_rdoc          = true
75  
-  s.extra_rdoc_files  = FileList["README*"].to_a
76  
-  s.rdoc_options << '--line-numbers' << '--inline-source'
77  
-  s.requirements << "ImageMagick"
78  
-  s.add_development_dependency 'shoulda'
79  
-  s.add_development_dependency 'jferris-mocha', '>= 0.9.5.0.1241126838'
80  
-  s.add_development_dependency 'aws-s3'
81  
-  s.add_development_dependency 'sqlite3-ruby'
82  
-  s.add_development_dependency 'activerecord'
83  
-  s.add_development_dependency 'activesupport'
  58
+desc 'Build the gemspec.'
  59
+task :gemspec do |t|
  60
+  exec 'gem build paperclip.gemspec'
84 61
 end
85 62
 
86 63
 desc "Print a list of the files to be put into the gem"
@@ -89,13 +66,13 @@ task :manifest => :clean do
89 66
     puts file
90 67
   end
91 68
 end
92  
- 
  69
+
93 70
 desc "Generate a gemspec file for GitHub"
94 71
 task :gemspec => :clean do
95 72
   File.open("#{spec.name}.gemspec", 'w') do |f|
96 73
     f.write spec.to_ruby
97 74
   end
98  
-end 
  75
+end
99 76
 
100 77
 desc "Build the gem into the current directory"
101 78
 task :gem => :gemspec do
17  features/basic.feature
... ...
@@ -0,0 +1,17 @@
  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"
27  features/s3.feature
... ...
@@ -0,0 +1,27 @@
  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"
14  features/step_definitions/html_steps.rb
... ...
@@ -0,0 +1,14 @@
  1
+Then %r{I should see an image with a path of "([^"]*)"} do |path|
  2
+  page.should have_css("img[src^='#{path}']")
  3
+end
  4
+
  5
+Then %r{^the file at "([^"]*)" is the same as "([^"]*)"$} do |web_file, path|
  6
+  expected = IO.read(path)
  7
+  actual = if web_file.match %r{^https?://}
  8
+    Net::HTTP.get(URI.parse(web_file))
  9
+  else
  10
+    visit(web_file)
  11
+    page.body
  12
+  end
  13
+  actual.should == expected
  14
+end
90  features/step_definitions/rails_steps.rb
... ...
@@ -0,0 +1,90 @@
  1
+Given "I have a rails application" do
  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"
  6
+    And I turn off class caching
  7
+    Given I save the following as "app/models/user.rb"
  8
+      """
  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 %>
  25
+      """
  26
+    And I save the following as "app/views/users/show.html.erb"
  27
+      """
  28
+      <p>Name: <%= @user.name %></p>
  29
+      <p>Avatar: <%= image_tag @user.avatar.url %></p>
  30
+      """
  31
+    And I run "script/generate paperclip user avatar"
  32
+    And the rails application is prepped and running
  33
+  }
  34
+end
  35
+
  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}`
  41
+  end
  42
+end
  43
+
  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) }
  47
+end
  48
+
  49
+When %r{I turn off class caching} do
  50
+  Dir.chdir(CUC_RAILS_ROOT) do
  51
+    file = "config/environments/test.rb"
  52
+    config = IO.read(file)
  53
+    config.gsub!(%r{^\s*config.cache_classes.*$},
  54
+                 "config.cache_classes = false")
  55
+    File.open(file, "w"){|f| f.write(config) }
  56
+  end
  57
+end
  58
+
  59
+When %r{the rails application is prepped and running$} do
  60
+  When "I reset the database"
  61
+  When "the rails application is running"
  62
+end
  63
+
  64
+When %r{I reset the database} do
  65
+  When %{I run "rake db:drop db:create db:migrate"}
  66
+end
  67
+
  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
  73
+end
  74
+
  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") 
  80
+end
  81
+
  82
+When %r{I run "([^"]*)"} do |command|
  83
+  Dir.chdir(CUC_RAILS_ROOT) do
  84
+    `#{command}`
  85
+  end
  86
+end
  87
+
  88
+When %r{I have a "([^"]*)" resource with "([^"]*)"} do |resource, fields|
  89
+  When %{I run "script/generate scaffold #{resource} #{fields}"}
  90
+end
9  features/step_definitions/s3_steps.rb
... ...
@@ -0,0 +1,9 @@
  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
  7
+
  8
+  assert_credentials(key, secret)
  9
+end
227  features/step_definitions/web_steps.rb
... ...
@@ -0,0 +1,227 @@
  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.
  6
+
  7
+
  8
+require 'uri'
  9
+require 'cgi'
  10
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
  11
+
  12
+module WithinHelpers
  13
+  def with_scope(locator)
  14
+    locator ? within(locator) { yield } : yield
  15
+  end
  16
+end
  17
+World(WithinHelpers)
  18
+
  19
+Given /^(?:|I )am on (.+)$/ do |page_name|
  20
+  visit path_to(page_name)
  21
+end
  22
+
  23
+When /^(?:|I )go to (.+)$/ do |page_name|
  24
+  visit path_to(page_name)
  25
+end
  26
+
  27
+When /^(?:|I )visit (\/.+)$/ do |page_path|
  28
+  visit page_path
  29
+end
  30
+
  31
+When /^(?:|I )press "([^"]*)"(?: within "([^"]*)")?$/ do |button, selector|
  32
+  with_scope(selector) do
  33
+    click_button(button)
  34
+  end
  35
+end
  36
+
  37
+When /^(?:|I )follow "([^"]*)"(?: within "([^"]*)")?$/ do |link, selector|
  38
+  with_scope(selector) do
  39
+    click_link(link)
  40
+  end
  41
+end
  42
+
  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
  53
+end
  54
+
  55
+# Use this to fill in an entire form with data from a table. Example:
  56
+#
  57
+#   When I fill in the following:
  58
+#     | Account Number | 5002       |
  59
+#     | Expiry date    | 2009-11-01 |
  60
+#     | Note           | Nice guy   |
  61
+#     | Wants Email?   |            |
  62
+#
  63
+# TODO: Add support for checkbox, select og option
  64
+# based on naming conventions.
  65
+#
  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
  71
+  end
  72
+end
  73
+
  74
+When /^(?:|I )select "([^"]*)" from "([^"]*)"(?: within "([^"]*)")?$/ do |value, field, selector|
  75
+  with_scope(selector) do
  76
+    select(value, :from => field)
  77
+  end
  78
+end
  79
+
  80
+When /^(?:|I )check "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
  81
+  with_scope(selector) do
  82
+    check(field)
  83
+  end
  84
+end
  85
+
  86
+When /^(?:|I )uncheck "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
  87
+  with_scope(selector) do
  88
+    uncheck(field)
  89
+  end
  90
+end
  91
+
  92
+When /^(?:|I )choose "([^"]*)"(?: within "([^"]*)")?$/ do |field, selector|
  93
+  with_scope(selector) do
  94
+    choose(field)
  95
+  end
  96
+end
  97
+
  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
  109
+end
  110
+
  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
  118
+  end
  119
+end
  120
+
  121
+Then /^(?:|I )should see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
  122
+  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
  129
+  end
  130
+end
  131
+
  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
  139
+  end
  140
+end
  141
+
  142
+Then /^(?:|I )should not see \/([^\/]*)\/(?: within "([^"]*)")?$/ do |regexp, selector|
  143
+  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
  150
+  end
  151
+end
  152
+
  153
+Then /^the "([^"]*)" field(?: within "([^"]*)")? should contain "([^"]*)"$/ do |field, selector, value|
  154
+  with_scope(selector) do
  155
+    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}/
  159
+    else
  160
+      assert_match(/#{value}/, field_value)
  161
+    end
  162
+  end
  163
+end
  164
+
  165
+Then /^the "([^"]*)" field(?: within "([^"]*)")? should not contain "([^"]*)"$/ do |field, selector, value|
  166
+  with_scope(selector) do
  167
+    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}/
  171
+    else
  172
+      assert_no_match(/#{value}/, field_value)
  173
+    end
  174
+  end
  175
+end
  176
+
  177
+Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should be checked$/ do |label, selector|
  178
+  with_scope(selector) do
  179
+    field_checked = find_field(label)['checked']
  180
+    if field_checked.respond_to? :should
  181
+      field_checked.should be_true
  182
+    else
  183
+      assert field_checked
  184
+    end
  185
+  end
  186
+end
  187
+
  188
+Then /^the "([^"]*)" checkbox(?: within "([^"]*)")? should not be checked$/ do |label, selector|
  189
+  with_scope(selector) do
  190
+    field_checked = find_field(label)['checked']
  191
+    if field_checked.respond_to? :should
  192
+      field_checked.should be_false
  193
+    else
  194
+      assert !field_checked
  195
+    end
  196
+  end
  197
+end
  198
+ 
  199
+Then /^(?:|I )should be on (.+)$/ do |page_name|
  200
+  current_path = URI.parse(current_url).path
  201
+  if current_path.respond_to? :should
  202
+    current_path.should == path_to(page_name)
  203
+  else
  204
+    assert_equal path_to(page_name), current_path
  205
+  end
  206
+end
  207
+
  208
+Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
  209
+  query = URI.parse(current_url).query
  210
+  actual_params = query ? CGI.parse(query) : {}
  211
+  expected_params = {}
  212
+  expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')} 
  213
+  
  214
+  if actual_params.respond_to? :should
  215
+    actual_params.should == expected_params
  216
+  else
  217
+    assert_equal expected_params, actual_params
  218
+  end
  219
+end
  220
+
  221
+Then /^I save and open the page$/ do
  222
+  save_and_open_page
  223
+end
  224
+
  225
+Then /^show me the page$/ do
  226
+  save_and_open_page
  227
+end
3  features/support/env.rb
... ...
@@ -0,0 +1,3 @@