Recently I worked on a Rails application with some kind of contact form: Data gets entered, validated, and sent by mail to a predefined recipient. No need for persistence, so a simple ActiveModel class is used:

1
2
3
4
5
6
# app/models/person.rb
class Person
  include ActiveModel::Model
  attr_accessor :name, :email, :age
  validates_presence_of :name, :email
end

The task was to extend the form with an upload field (the uploaded document would then be added as attachment to the mail). The upload should persist across form redisplays, so using a plain file input was not an option.

CarrierWave is a great solution for handling file uploads, and it also supports form redisplays. Normally it’s used with ActiveRecord or Mongoid models – but can we also make it work with a simple ActiveModel class?

Turns out this is quite easy: We simply extend the model class with CarrierWave::Mount and voilà, we now can use mount_uploader as usual:

1
2
3
4
5
6
7
8
9
10
11
12
13
# app/models/person.rb
class Person

  # … (existing code snipped) …

  extend CarrierWave::Mount
  mount_uploader :profile, AttachmentUploader
end

# app/uploaders/attachment_uploader.rb
class AttachmentUploader < CarrierWave::Uploader::Base
  storage :file
end

With the appropriate changes to the form (don’t forget the hidden “cache” field for form redisplays and multipart: true), everything already works as expected.

Adding validations to the upload

Now, what if we want to allow only certain kinds of files? CarrierWave also supports this, but the validations won’t work out of the box with ActiveModel. We have to manually require the validations module and explicitly activate the validations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# app/models/person.rb
require 'carrierwave/validations/active_model'
class Person

  # … (existing code snipped) …

  validates_with CarrierWave::Validations::ActiveModel::IntegrityValidator,
    attributes: %i(profile)
end

# app/uploaders/attachment_uploader.rb
class AttachmentUploader < CarrierWave::Uploader::Base

  # … (existing code snipped) …

  def extension_white_list
    %w(pdf jpg)
  end
end

And that’s it! We now have an ActiveModel model supporting file uploads, including validations and form redisplay!

Complete code

Here’s the complete code for our model and uploader classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# app/models/person.rb
require 'carrierwave/validations/active_model'
class Person
  include ActiveModel::Model
  extend CarrierWave::Mount
  attr_accessor :name, :email, :age
  mount_uploader :profile, AttachmentUploader
  validates_presence_of :name, :email
  validates_with CarrierWave::Validations::ActiveModel::IntegrityValidator,
    attributes: %i(profile)
end

# app/uploaders/attachment_uploader.rb
class AttachmentUploader < CarrierWave::Uploader::Base
  storage :file

  def extension_white_list
    %w(pdf jpg)
  end
end

We offer Ruby on Rails consulting based on over 11 years of experience, going back to Rails 0.9.3 in 2005. Want to know more?