{"id":48262,"date":"2013-05-23T12:08:08","date_gmt":"2013-05-23T09:08:08","guid":{"rendered":"https:\/\/www.altoros.com\/blog\/?p=48262"},"modified":"2021-09-03T20:06:26","modified_gmt":"2021-09-03T17:06:26","slug":"organizing-storage-in-multiple-fog-containers-using-carrierwave","status":"publish","type":"post","link":"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/","title":{"rendered":"Organizing Storage in Multiple Fog Containers Using CarrierWave"},"content":{"rendered":"\n<p><a href=\"https:\/\/github.com\/carrierwaveuploader\/carrierwave\">CarrierWave<\/a> is one of the most popular Ruby-on-Rails solutions for file upload and storage. Most of us used it more than once. In this blog post, I want to dig into how flexible the solution really is. First, we will look a little deeper into how it works on lower levels (feel free to skip that part if you know stuff) and then watch how you can take advantage of its flexibility on a couple of simple scenarios.<\/p>\n<p>&nbsp;<\/p>\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_79_2 counter-hierarchy ez-toc-counter ez-toc-transparent ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#How_it_works\" >How it works<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#How_to_use_CarrierWave_to_take_advantage\" >How to use CarrierWave to take advantage<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#Conclusion\" >Conclusion<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#Further_reading\" >Further reading<\/a><\/li><\/ul><\/nav><\/div>\n<h3><span class=\"ez-toc-section\" id=\"How_it_works\"><\/span>How it works<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>First of all, CarrierWave has a number of modules extending your object-relational mapping (ORM) classes. The gem itself includes the extension only for <code style=\"color: black; background-color: #e6e6e6;\">ActiveRecord<\/code>, but everything else is readily available. All it does is creating a class method\u2014<code style=\"color: black; background-color: #e6e6e6;\">mount_uploader<\/code>\u2014that receives any string parameter of an ORM model.<\/p>\n<pre class=\"brush: ruby; title: ; notranslate\" title=\"\">\r\n# == Schema Information\r\n#&lt;\r\n# Table name: items\r\n#\r\n#  id         :integer          not null, primary key\r\n#  file       :string(255)\r\n\r\nclass Item &amp;lt; ActiveRecord::Base\r\n  mount_uploader :file\r\nend<\/pre>\n<p>Every time you instantiate an object of the <code style=\"color: black; background-color: #e6e6e6;\">Item<\/code> class, your ORM downloads the data from your database and instantiates the object as usual. Now, there is an object of the <code style=\"color: black; background-color: #e6e6e6;\">Uploader::Base<\/code> class, mounted where just a string parameter should have been. Methods such as <code style=\"color: black; background-color: #e6e6e6;\">file<\/code> and <code style=\"color: black; background-color: #e6e6e6;\">file=<\/code> don\u2019t access data in the <code style=\"color: black; background-color: #e6e6e6;\">@file variable<\/code> directly, as there is a middle man now.<\/p>\n<p>The <code style=\"color: black; background-color: #e6e6e6;\">file=<\/code> setter method of a new object now proceeds through the following steps:<\/p>\n<ol>\n<li style=\"margin-bottom: 6px\">Accepts an object of any class extending <code style=\"color: black; background-color: #e6e6e6;\">File<\/code>, including various types of streams, such as <code style=\"color: black; background-color: #e6e6e6;\">Tempfile<\/code> and <code style=\"color: black; background-color: #e6e6e6;\">ActionDispatch::Http::UploadedFile<\/code>. This is the object you receive from HTTP multipart file uploads.<\/li>\n<li style=\"margin-bottom: 6px\">Caches the received file in a temporary directory locally.<\/li>\n<li style=\"margin-bottom: 6px\">Assigns the file\u2019s name to our <code style=\"color: black; background-color: #e6e6e6;\">@file variable<\/code>. Unlike some other <code style=\"color: black; background-color: #e6e6e6;\">upload and store<\/code> gems, CarrierWave doesn\u2019t save a full file path to a database, rather its original name only. The responsibility of building the full path is delegated to the <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> object.<\/li>\n<li style=\"margin-bottom: 6px\">Waits for an object to be persisted.<\/li>\n<li style=\"margin-bottom: 6px\">Optionally conducts file processing, resulting in one or more new files.<\/li>\n<li style=\"margin-bottom: 6px\">Copies a file or processed files from a temporary storage to a persistant storage. Again, exact location and specifics of a storage is determined by the <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> object.<\/li>\n<\/ol>\n<p>When you update the existing object, the process is generally the same with the addition of <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> that downloads a previously uploaded version, caches it, and restores if a persisting object vailed.<\/p>\n<p>So, how exactly does <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> choose where and how to store the uploaded file and how to retrieve it? <code style=\"color: black; background-color: #e6e6e6;\">Carrierwave::Uploader::Base<\/code> has a large number of defined default methods. Some of them are grouped into the <code style=\"color: black; background-color: #e6e6e6;\">Strategy<\/code> modules, specifying each aspect of the process (names for cache\/storage directories, specifics of processing, etc.). These methods also have  access to model objects <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> is mounted on, which means we can tweak handling of each uploaded file.<\/p>\n<p>Of course, there is the <code style=\"color: black; background-color: #e6e6e6;\">Carrierwave.configure<\/code> method accepting a block of configurations. However, it merely provides default results for these uploader instance methods.<\/p>\n<p>CarrierWave has two main storage strategies: <code style=\"color: black; background-color: #e6e6e6;\">:file<\/code> for a local storage and <code style=\"color: black; background-color: #e6e6e6;\">:fog<\/code> for a remote storage. A user can add other strategies as long as they support storing and retrieving, of course, but the above-mentioned strategies already cover most options. This means, the <code style=\"color: black; background-color: #e6e6e6;\">fog<\/code> storage strategy is itself a delegation to the <a href=\"https:\/\/github.com\/fog\/fog\" rel=\"noopener noreferrer\" target=\"_blank\">fog\/fog<\/a> gem, which provides a common API for <a href=\"http:\/\/fog.io\/about\/supported_services.html\" rel=\"noopener noreferrer\" target=\"_blank\">multiple cloud storage solutions<\/a>. To initialize the CarrierWave fog storage, you should provide a service name, a container name, and credentials.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"How_to_use_CarrierWave_to_take_advantage\"><\/span>How to use CarrierWave to take advantage<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>All code snippets given here are excerpts of a more complex working application. This means, some stuff could be left out, and some become more complex while I was trying to simplify the code.<\/p>\n<p>&nbsp;<br \/>\n<b>1. Specifying upload directories<\/b><\/p>\n<p>Both default storage options rely on a number of uploader methods to detemine how to handle uploads. The main option is the <code style=\"color: black; background-color: #e6e6e6;\">store_dir<\/code> method that by default looks like this.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ndef store_dir\r\n  &quot;uploads\/#{model.class.to_s.underscore}\/#{mounted_as}\/#{model.id}&quot;\r\nend\r\n<\/pre>\n<p>Uploaded files will be stored locally in a number of nested folders in a public directory of the application, with each file having its own folder.<\/p>\n<ul>\n<li style=\"margin-bottom: 6px\">Generally, we try to normalize a database in such a way that a single class has a single file field maximum, so that the <code style=\"color: black; background-color: #e6e6e6;\">mounted_at<\/code> parameter could be left out.<\/li>\n<li style=\"margin-bottom: 6px\">On your development machine, you will probably run an application on different environments (at least test). So, consider adding <code style=\"color: black; background-color: #e6e6e6;\">Rails.env<\/code> to <code style=\"color: black; background-color: #e6e6e6;\">store_dir<\/code>.<\/li>\n<li style=\"margin-bottom: 6px\">Relying on a class name of the model can render data inconsistently if you refactor the model. <code style=\"color: black; background-color: #e6e6e6;\">File<\/code> will still be there, but <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> will be unable to retrieve it, since it will look in a wrong place. I prefer to implicitly set a folder name in each <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code>.<\/li>\n<\/ul>\n<p>Anyway, now, you can see that by overwriting the <code style=\"color: black; background-color: #e6e6e6;\">store_dir<\/code> method in your uploaders, you can store your uploads in any way you like. For example, you can group files by their creator\u2019s identity rather than by their type.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nclass GeneralApplicationUploader &amp;lt; Carrierwave::Uploader::Base\r\n  \r\n  def store_dir\r\n    folder = respond_to?(:folder_name) ? folder_name : model.class.to_s.underscore\r\n    base_upload_dir &amp;lt;&amp;lt; &quot;#{folder}\/#{model.id}&quot;\r\n  end\r\n\r\n  private\r\n\r\n def base_upload_dir\r\n    &quot;uploads\/#{Rails.env}\/&quot;\r\n  &lt;span class=&quot;k&quot;&gt;end&lt;\/span&gt;\r\n\r\nend\r\n\r\nclass ItemUploader &amp;lt; GeneralApplicationUploader\r\n\r\n  def folder_name\r\n   'items'\r\n  end\r\n\r\nend\r\n<\/pre>\n<p>There is one thing you should always remember, though: once a file was uploaded, any change to how <code style=\"color: black; background-color: #e6e6e6;\">store_dir<\/code> resolves will prevent <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> from finding that file.<\/p>\n<p>&nbsp;<br \/>\n<b>2. Differences in handling local and cloud storages<\/b><\/p>\n<p>Now, if you expect a lot of upload\/download activity, and you have an option of using a remote storage in production, you should definitely do that. Using a remote storage for development or test environments, on the other hand, can be troublesome (that is, too expensive).<\/p>\n<p>Ideally, strategies for handling the <code style=\"color: black; background-color: #e6e6e6;\">file<\/code> and <code style=\"color: black; background-color: #e6e6e6;\">fog<\/code> storages should behave in the exact same way. For most cases, they do. It is up to you to decide on whether you should develop an application using a cloud storage all the time or cut your expenses and develop using a local storage, being prepared to deal with a few differences.<\/p>\n<p>If you, for example, have a number of text documents stored, and you want to show documents&#8217; text on a page, there will be a difference. It will be there, because the <code style=\"color: black; background-color: #e6e6e6;\">fog<\/code> storage mostly handles file URLs and passes them to a client browser not a server.<\/p>\n<p>There is a method you can employ to check if you are using a local or a remote storage.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nclass TextUploader &amp;lt; GeneralApplicationUploader\r\n  def store_local?\r\n    _storage == CarrierWave::Storage::File\r\n  end\r\n\r\n  # Then the text file content can be accessed as:\r\n\r\n  def body \r\n    store_local? ? File.read(path) : open(url).&lt;read&gt;\r\n  end\r\nend\r\n\r\nclass Item &amp;lt; ActiveRecord::Base\r\n  mount_uploader :file, TextUploader\r\nend\r\n<\/pre>\n<p>This way, <code style=\"color: black; background-color: #e6e6e6;\">Item.first.file.body<\/code> will return the same text regardless of whether a file is stored remotely or locally.<\/p>\n<p>&nbsp;<br \/>\n<b>3. Safe file names<\/b><\/p>\n<p>By default, CarrierWave already sanitizes the name of a file it receives, keeping only English letters and numbers. There is also a configuration option that helps you to keep all unicode characters. It also helps you to avoid <a href=\"https:\/\/guides.rubyonrails.org\/security.html#file-uploads\" rel=\"noopener noreferrer\" target=\"_blank\">file path injection vulnerabilities<\/a>. However, storing a file with its name unchanged still has some disadvantages. For example, one can upload a file with a name so long that saving it causes an exception on the file system level. To avoid this, we rename all files that are saved to the system, encoding old file names.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">  \r\ndef full_filename(for_file)\r\n    original_name = for_file || model.read_attribute(:mounted_as)\r\n   &#x5B;Digest::MD5.hexdigest(original_name), File.extname(original_name)].join\r\n end\r\n<\/pre>\n<p>Above the <code style=\"color: black; background-color: #e6e6e6;\">full_filename<\/code> method is used both on storing a file (where <code style=\"color: black; background-color: #e6e6e6;\">for_file<\/code> is a file name of an incoming upload) and on retrieving a file (when it is nil). The good part is that the name is stored in a database in its original state, while on a disk, it is properly encoded.<\/p>\n<p>Now, you can provide this file for download, with it retaining the original file name, using this link.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n= link_to 'Download', item.file.url, download: item&#x5B;:file], target: '_blank'\r\n<\/pre>\n<p>Cached files are also saved to the disk, so, we will probably have to encode their names too.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">  \r\ndef cache_name\r\n    if cache_id &amp;amp;&amp;amp; original_filename\r\n      name = Digest::MD5.hexdigest(full_original_filename + cache_id)\r\n      extension = File.extname(full_original_filename) \r\n      &#x5B;name, extension].join\r\n    end\r\n  end\r\n<\/pre>\n<p>&nbsp;<br \/>\n<b>4. Switching remote storage containers of a file<\/b><\/p>\n<p>In some cloud file storages, for example on <a href=\"https:\/\/www.rackspace.com\/\" rel=\"noopener noreferrer\" target=\"_blank\">Rackspace<\/a>, a file can be stored in two types of containers.<\/p>\n<ul>\n<li style=\"margin-bottom: 6px\"><b>Public<\/b>. A file is readily available for download via HTTP, sometimes even with the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Content_delivery_network\" rel=\"noopener noreferrer\" target=\"_blank\">container delivery network<\/a> (CDN) support. Content is delivered quickly, but you can\u2019t even dream of many security features, such as hotlinking protection.<\/li>\n<li style=\"margin-bottom: 6px\"><b>Private<\/b>. A file is available only via a SSL-secured temporary link. However, a file is difficult to download other than with your application, as CDN is unavailable.<\/li>\n<\/ul>\n<p>Let\u2019s imagine that we need to store some type of files, but it is a user who decides whether it should be publicly available or hidden. Naturally, we will want the interface to be as simple as possible.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n# == Schema Information\r\n#\r\n# Table name: items\r\n#\r\n#  id         :integer          not null, primary key\r\n#  file       :string(255)\r\n#  hidden     :boolean          not null, default(FALSE)\r\n\r\nclass Item &amp;lt; ActiveRecord::Base\r\n  mount_uploader :file, SwitchingStoragesUploader\r\nend\r\n<\/pre>\n<p><code style=\"color: black; background-color: #e6e6e6;\">Uploader::Base<\/code> has two methods\u2014<code style=\"color: black; background-color: #e6e6e6;\">fog_public<\/code> and <code style=\"color: black; background-color: #e6e6e6;\">fog_directory<\/code>\u2014that decide where to store uploads.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nclass SwitchingStoragesUploader &amp;lt; GeneralApplicationUploader\r\n  \r\n  def fog_public\r\n    !(model.respond_to?(:hidden) &amp;amp;&amp;amp; (model.hidden_changed? ? model.hidden_was : model. hidden))\r\n  end\r\n  \r\n  def fog_directory\r\n    fog_public ? 'public_container_name' : 'private_container_name'\r\n  end\r\n   \r\nend\r\n<\/pre>\n<p>This way, when you create a new object of the class <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> is mounted on, CarrierWave chooses a storage depending on the persisted value of the <code style=\"color: black; background-color: #e6e6e6;\">hidden<\/code> field. If you change that parameter on the existing object, though, without touching the file field, an uploaded file won\u2019t move, and the reference to it will be lost.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nclass SwitchingStoragesUploader &amp;lt; GeneralApplicationUploader\r\n  \r\n  def initialize(*)\r\n    if model.respond_to? :hidden\r\n      model.define_method(:hidden=) do |new_value|\r\n        send &quot;#{ mounted_as }_will_change!&quot;\r\n        super(new_value)\r\n      end\r\n    end\r\n    super\r\n  end\r\n   \r\nend \r\n<\/pre>\n<p>That redefinition method, though hacky, will make <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> work as expected: when you change a value of the <code style=\"color: black; background-color: #e6e6e6;\">hidden<\/code> parameter, <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> downloads a file from an old container, deletes the file from the storage, then uploads a cached file to a new container after the object is persisted.<\/p>\n<p>Do we really want to burden our application server with upload\/download routines all the time? I think, we don\u2019t. As I mentioned before, all interactions with cloud storages are conducted through the <a href=\"https:\/\/github.com\/fog\/fog\">fog<\/a> gem that provides a common API for these storage services. This API is, in fact, wider than it is used for CarrierWave functionality.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nclass SwitchingStoragesUploader &amp;lt; GeneralApplicationUploader\r\n\r\n def initialize(*)\r\n    if model.respond_to? :hidden\r\n    \r\n      if model.persisted? &amp;amp;&amp;amp; model.hidden_changed?\r\n        def model.before_save(model)\r\n          model.send(mounted_as).copy_with_fog\r\n        end\r\n      end\r\n      \r\n    end\r\n    \r\n    super\r\n  end\r\n  \r\n  def copy_with_fog\r\n    unless store_local? || mounted_column_changed?\r\n      begin\r\n      \r\n        source_container = fog_directoy\r\n        target_container = fog_public? 'private_container_name' : 'public_container_name'\r\n        \r\n        fog_api = Fog::Storage.new(fog_credentials)\r\n         \r\n        fog_api.copy_object(source_container, store_dir, target_container, store_dir&gt;)\r\n        fog_api.delete_object(source_container, store_dir)\r\n          \r\n      rescue Fog::Errors::Error\r\n        model.errors.add(mounted_as, 'Error occured while migrating file in storage!'))\r\n        false\r\n      end\r\n    end\r\n  end \r\n  \r\n def mounted_column_changed?\r\n    model.public_send(mounted_as).cached?.present?\r\n end \r\n   \r\nend\r\n<\/pre>\n<p>The <code style=\"color: black; background-color: #e6e6e6;\">mounted_column_changed?<\/code> method skips the process of copying if a new file is provided. In that case, a new file will be stored to a new container anyway. That\u2019s also why we check if the model was persisted before.<\/p>\n<p>As you can see, all we do is initialize an object of <code style=\"color: black; background-color: #e6e6e6;\">Fog::Storage<\/code>, use it to copy a file from one container to another, then delete the file in an old container. If this, somehow, fails, we add an error to the model and cancel <b>Save<\/b>, providing a level of <a href=\"https:\/\/en.wikipedia.org\/wiki\/Exception_safety\" rel=\"noopener noreferrer\" target=\"_blank\">strong exception safety<\/a> to the process.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>The approach is simple: control as much of the process as possible by application entities, not by storing full paths to a database. For this purpose, nearly every aspect of that process is arranged as a method defined on the <code style=\"color: black; background-color: #e6e6e6;\">Uploader<\/code> object, giving a developer a good level of flexibility. CarrierWave is a nice tool to help you store your files, but, like any tool, it requires <a href=\"https:\/\/tvtropes.org\/pmwiki\/pmwiki.php\/Main\/PossessionImpliesMastery\" rel=\"noopener noreferrer\" target=\"_blank\">some knowledge<\/a> to handle it well.<\/p>\n<p>&nbsp;<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Further_reading\"><\/span>Further reading<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li><a href=\"https:\/\/www.altoros.com\/blog\/s3-storage-for-your-rails-application\/\">S3 Storage for your Rails application<\/a><\/li>\n<li><a href=\"https:\/\/www.altoros.com\/blog\/deploying-a-rails-5-app-with-mongodb-redis-and-carrierwave-to-ibm-bluemix\/\">Deploying a Rails 5 App with MongoDB, Redis, and CarrierWave to IBM Bluemix<\/a><\/li>\n<li><a href=\"https:\/\/www.altoros.com\/blog\/the-ibm-bluemix-object-storage-service-in-ruby-projects\/\">Using IBM Bluemix Object Storage in Ruby Projects<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>CarrierWave is one of the most popular Ruby-on-Rails solutions for file upload and storage. Most of us used it more than once. In this blog post, I want to dig into how flexible the solution really is. First, we will look a little deeper into how it works on lower [&#8230;]<\/p>\n","protected":false},"author":119,"featured_media":58163,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"content-type":"","footnotes":"","_links_to":"","_links_to_target":""},"categories":[214],"tags":[1000,895],"class_list":["post-48262","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-github","tag-research-and-development"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.6 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Organizing Storage in Multiple Fog Containers Using CarrierWave | Altoros<\/title>\n<meta name=\"description\" content=\"This blog post explores CarrierWave capabilities and how to benefit from the solution&#039;s flexibility.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Organizing Storage in Multiple Fog Containers Using CarrierWave | Altoros\" \/>\n<meta property=\"og:description\" content=\"Carrierwave gem and it&#039;s inner workings, organizing switching content between private and public containers\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/\" \/>\n<meta property=\"og:site_name\" content=\"Altoros\" \/>\n<meta property=\"article:published_time\" content=\"2013-05-23T09:08:08+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2021-09-03T17:06:26+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2013\/05\/optimizing-storage-in-fog-containers-carrierwave-featured-image.gif\" \/>\n\t<meta property=\"og:image:width\" content=\"1182\" \/>\n\t<meta property=\"og:image:height\" content=\"423\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/gif\" \/>\n<meta name=\"author\" content=\"Dmitry Savitski\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Dmitry Savitski\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/\",\"url\":\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/\",\"name\":\"Organizing Storage in Multiple Fog Containers Using CarrierWave | Altoros\",\"isPartOf\":{\"@id\":\"https:\/\/www.altoros.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2013\/05\/optimizing-storage-in-fog-containers-carrierwave-featured-image.gif\",\"datePublished\":\"2013-05-23T09:08:08+00:00\",\"dateModified\":\"2021-09-03T17:06:26+00:00\",\"author\":{\"@id\":\"https:\/\/www.altoros.com\/blog\/#\/schema\/person\/a787fe0020308b728e68ad4a4ff571ca\"},\"description\":\"Carrierwave gem and it's inner workings, organizing switching content between private and public containers\",\"breadcrumb\":{\"@id\":\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#primaryimage\",\"url\":\"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2013\/05\/optimizing-storage-in-fog-containers-carrierwave-featured-image.gif\",\"contentUrl\":\"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2013\/05\/optimizing-storage-in-fog-containers-carrierwave-featured-image.gif\",\"width\":1182,\"height\":423},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.altoros.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Organizing Storage in Multiple Fog Containers Using CarrierWave\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.altoros.com\/blog\/#website\",\"url\":\"https:\/\/www.altoros.com\/blog\/\",\"name\":\"Altoros\",\"description\":\"Insight\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.altoros.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.altoros.com\/blog\/#\/schema\/person\/a787fe0020308b728e68ad4a4ff571ca\",\"name\":\"Dmitry Savitski\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.altoros.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2019\/07\/dmitry-savitski-96x96.jpeg\",\"contentUrl\":\"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2019\/07\/dmitry-savitski-96x96.jpeg\",\"caption\":\"Dmitry Savitski\"},\"description\":\"Dmitry Savitski is a software engineer at Altoros. He specializes in web development using Ruby and JavaScript as his primary tools. Dmitry has a keen interest in the 12-factor application approach and the ways in which different Cloud Foundry implementations\u2014including Predix\u2014support this methodology.\",\"url\":\"https:\/\/www.altoros.com\/blog\/author\/dmitry-savitski\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Organizing Storage in Multiple Fog Containers Using CarrierWave | Altoros","description":"This blog post explores CarrierWave capabilities and how to benefit from the solution's flexibility.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/","og_locale":"en_US","og_type":"article","og_title":"Organizing Storage in Multiple Fog Containers Using CarrierWave | Altoros","og_description":"Carrierwave gem and it's inner workings, organizing switching content between private and public containers","og_url":"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/","og_site_name":"Altoros","article_published_time":"2013-05-23T09:08:08+00:00","article_modified_time":"2021-09-03T17:06:26+00:00","og_image":[{"width":1182,"height":423,"url":"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2013\/05\/optimizing-storage-in-fog-containers-carrierwave-featured-image.gif","type":"image\/gif"}],"author":"Dmitry Savitski","twitter_misc":{"Written by":"Dmitry Savitski","Est. reading time":"11 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/","url":"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/","name":"Organizing Storage in Multiple Fog Containers Using CarrierWave | Altoros","isPartOf":{"@id":"https:\/\/www.altoros.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#primaryimage"},"image":{"@id":"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#primaryimage"},"thumbnailUrl":"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2013\/05\/optimizing-storage-in-fog-containers-carrierwave-featured-image.gif","datePublished":"2013-05-23T09:08:08+00:00","dateModified":"2021-09-03T17:06:26+00:00","author":{"@id":"https:\/\/www.altoros.com\/blog\/#\/schema\/person\/a787fe0020308b728e68ad4a4ff571ca"},"description":"Carrierwave gem and it's inner workings, organizing switching content between private and public containers","breadcrumb":{"@id":"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#primaryimage","url":"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2013\/05\/optimizing-storage-in-fog-containers-carrierwave-featured-image.gif","contentUrl":"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2013\/05\/optimizing-storage-in-fog-containers-carrierwave-featured-image.gif","width":1182,"height":423},{"@type":"BreadcrumbList","@id":"https:\/\/www.altoros.com\/blog\/organizing-storage-in-multiple-fog-containers-using-carrierwave\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.altoros.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Organizing Storage in Multiple Fog Containers Using CarrierWave"}]},{"@type":"WebSite","@id":"https:\/\/www.altoros.com\/blog\/#website","url":"https:\/\/www.altoros.com\/blog\/","name":"Altoros","description":"Insight","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.altoros.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.altoros.com\/blog\/#\/schema\/person\/a787fe0020308b728e68ad4a4ff571ca","name":"Dmitry Savitski","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.altoros.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2019\/07\/dmitry-savitski-96x96.jpeg","contentUrl":"https:\/\/www.altoros.com\/blog\/wp-content\/uploads\/2019\/07\/dmitry-savitski-96x96.jpeg","caption":"Dmitry Savitski"},"description":"Dmitry Savitski is a software engineer at Altoros. He specializes in web development using Ruby and JavaScript as his primary tools. Dmitry has a keen interest in the 12-factor application approach and the ways in which different Cloud Foundry implementations\u2014including Predix\u2014support this methodology.","url":"https:\/\/www.altoros.com\/blog\/author\/dmitry-savitski\/"}]}},"_links":{"self":[{"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/posts\/48262","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/users\/119"}],"replies":[{"embeddable":true,"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/comments?post=48262"}],"version-history":[{"count":23,"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/posts\/48262\/revisions"}],"predecessor-version":[{"id":63392,"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/posts\/48262\/revisions\/63392"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/media\/58163"}],"wp:attachment":[{"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/media?parent=48262"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/categories?post=48262"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.altoros.com\/blog\/wp-json\/wp\/v2\/tags?post=48262"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}