parm530
6/21/2018 - 1:25 PM

Image Uploading & Carrierwave

Understanding how carrierwave implements image uploading

Image Uploading using Basic Rails


Structure

  • In the root of your rails app, there is public folder used to hold static files!
    • This folder is hit before Rails is! For example, inside this public folder, there static html error pages (404), if you go to your browser and type localhost:3000/404 you will see that page renders immediately, Rails does not need to process it and serve it (no database lookups or route lookups)
  • Inside public, you can create a file that will store the images for your model!
  • You will need to design the file structure of the images: for example, using a Book model:
    • cd public && mkdir books
  • What if we have several images for the book? (Front cover, back cover, author image ...)
  • Before that, you can use the id of the model object to create dynamic folders for each object
    • Book with an id of 2 will have it images located in the path: public/books/2/
    • Possible image paths for different types of images: public/books/2/cover, public/books/2/back, public/books/2/author
    • You can then save the image in the respective folder!
    • Then in your views, to show the image: <%= image_tag "/books/#{@book.id}/cover/cover.jpg" %>
  • Now the image will appear. Next, add the uploader to your edit page:
    • You will need to add a database field to your model for the image, this way you can save that attribute and be able to change it!
    • Then, in your form, you can add the new field via: <%= f.file_field :db_name %>
    • **Add the new changes to your permitted params into your controller! **
    • In the model, add an attr_accessor for the database name field!
    • Add an after_save :save_cover_image, if: :cover callback: which allows us to take the file that was uploaded and save it to the public directory!
class Book
  after_save :save_cover_image, if: :cover
  
  def save_cover_image
    filename = cover.original_filename #given by the file upload
    folder = "public/books/#{id}/cover"
    
    FileUtils::mkdir_p folder # generates the given folder path rather then checking to see
                              # if each path exists one by one
                              
    f = File.open File.join(folder, filename), "wb"
    f.write cover.read()
    f.close
    
    self.cover = nil
    update cover_filename: filename # cover_filename is the actual database name in the books table
  
  end
end
  • The above code creates an attribute reader and writer method called cover.

  • The object has a database field called cover_filename (which is a string).

  • After an object has created or updated, the after_save callback is used to store the file in your rails app (within the public directory), then the object has its cover_filename attribute updated

  • Then you can edit the show page: <%= image_tag "/books/#{@book.id}/cover/#{@book.cover_filename}" if @book.cover_filename? %>


Using Carrierwave

  • Install carrierwave via gemfile: gem 'carrierwave', ~> 0.10.0
  • bundle install
  • rails g uploader Cover (Cover for example)
  • Make sure your model has a db column named after the uploader (so it would have to be cover, not cover_filename like above)
  • In your model class, mount_uploader :cover, CoverUploader
  • In your form, to add the field: <%= modelclass.file_field :cover %>
  • Finally, in your view: <%= image_tag(@post.image.url, alt: 'Image') if @post.image? %>

Understanding Image Dimensions

  • To be able to specify image dimensions upon uploading:
    • Uncomment the line of code include Carrierwave::MiniMagick
      • Used for creating versions (different scaled sizes of an image)
    • If you only ever need just one version, you can use the resize_to_fill method option:
      • process resize_to_fill: [200, 100], for example
    • 3 options for image resizing: resize_to_limit, resize_to_fill, resize_to_fit

Adding Versions (webp)

# Create different versions of your uploaded files:
version :optimised do
  process convert: 'webp'
  process :set_content_type_to_webp

  def full_filename(for_file = model.file_name.file)
    extension = File.extname(for_file)
    "#{for_file.sub(extension, '.webp')}"
  end

  def exists?
    file&.exists?
  end
end

protected
# Required to actually force Amazon S3 to treat it like an image
def set_content_type_to_webp
  file.instance_variable_set(:@content_type, 'image/webp')
end

To recreate versions for an image with a particular version:

instance = MyUploader.new
instance.recreate_versions!(:thumb)

Multiple Uploading to Carrierwave using Nested Attributes (1-M Association) NO AJAX

  • This will include 2 models: Model A and Model B
  • Model A has_many of Model B
  • Model B belongs_to Model A
# in model_a.rb
has_many :model_bs

accepts_nested_attributes_for :model_bs 
# make sure this is plural

# -------------------------------------------
# in model_b.rb
belongs_to :model_a

mount_uploader .... # used for carrierwave
# make sure that this model has a db field named after the uploader
# t.references :model_a # singular!!!

# -------------------------------------------

# in model_a_controller.rb

def new
  @modela = ModelA.new
  @modela.modelbs.build
end


def create
  # normal stuff
end


private

def modela_params
  params.require().permit(model_bs_attributes: [database names for model b])
  # MUST BE PLURAL model_bS_attributeS: []
end

# -------------------------------------------

# in the form for creating this new model

<%= form_for ....
  #...other code
  <%= f.fields_for :model_bs do |ff| %>
    <%= ff.label ...%>
    <%= ff.file_field :uploader, multiple: true, name: "model_a[model_b_attributes][][uploader]" %>
  <% end %>
<% end %>

  • Be careful with the naming conventions used in rails (source of most errors)
  • No need to use jquery uploader, the submit action alone will suffice for the creation of multiple objects