Leveraging Active Storage for Efficient File Management in a Calorie Tracker Application
Published: 2023-04-10 21:42:19

Active Storage for Efficient File Management in a Calorie Tracker Application


Active Storage is a powerful and flexible file management solution for Ruby on Rails applications. It facilitates the process of uploading, storing, and processing files by abstracting away the complexities of dealing with various storage services. In my calorie tracker application I incorporated Active Storage in order to manage user uploaded meal images. 

Getting started with active storage begins with running the following command: 
rails active_storage:install 
This command generates a migration that creates three tables named active_storage_blobs, active_storage_variant_records, and active_storage_attachments. 
Next we have to let active storage know where we are storing the user uploaded files. For the purposes of this application I chose local. 
config.active_storage.service = :local

 Upon creating my Meal model I added an :image attribute that will represent the user uploaded image.
rails g model meal name, calories, image
In my meal model I now have access to the macro has_one_attached which lets each meal have one file attached to it. 
class Meal < ApplicationRecord
   has_one_attached :image

Next in my Meals Controller I permit the :image attribute in my meals params method,
def meal_params
       params.require(:meal).permit(:name, :calories, :image)
end

Now that I have the backend setup for meals, I can now create a form in my frontend that accepts an image upload. 

<h1>Add Today's Meals</h1>
           <form className='form' encType="multipart/form-data" onSubmit={handleSubmit }>
               <div>
                   <label htmlFor="name">Name:</label>
                   <input type="text" id="name" name="name" value={ formData.name } onChange={ handleChange } />
               </div>
               <div>
                   <label htmlFor="calories">Calories:</label>
                   <input type="number" id="calories" name="calories" value={ formData.calories } onChange={ handleChange } />
               </div>
               <div>
                   <label htmlFor="image">Upload an image:</label>
                   <input type="file" id="image" name="image" accept="image/*" onChange={ handleChange } />
               </div>
               <div>
                   <input type="submit" value="Add Meal" />
               </div>
           </form>

Now when a user submits the form with an image, it will be automatically associated with the meal using Active Storage. 
Here is the handleSubmit function for submitting the form,
const handleSubmit = (e) => {
       e.preventDefault();
       dispatch(clearErrors());
       const data = new FormData();


       data.append("meal[name]", e.target.name.value);
       data.append("meal[calories]", e.target.calories.value);


       if (e.target.image.files[0] !== undefined ) {
           data.append("meal[image]", e.target.image.files[0]);
       }
     
       dispatch(addMeal(data))
  
Since we are making a post to an API, using the new FormData provides an easy way to handle file uploads without needing to encode the file data manually and packaging it into a format that can be sent to the server using fetch api. 

For my application I want to display these images for the user, however one thing active storage doesn’t do automatically is create urls for the images that can be used as the src for image tags. 

In order to do this, I created a custom method in the meal model,
class Meal < ApplicationRecord
   has_one_attached :image


   has_many :user_meals, dependent: :destroy
   has_many :users, through: :user_meals
  
   validates :name, :calories, presence: true
   validates :calories, numericality: { only_integer: true }


   def image_url
       Rails.application.routes.url_helpers.url_for(image) if image.attached?
   end
end

Image_url generates a full url for an attached image if it exists.
If I now take a look at all the meals that are added I can see that there is an image_url key,
  "id": 9,
       "name": "Rice, Spaghetti, and Chicken bites",
       "calories": 850,
       "created_at": "2023-04-04T13:48:23.407Z",
       "image_url": "http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--2ac2835b2e1a0f3604c23770ab5eff527e690d4c/Rice,%20Spaghetti,%20Chicken.jpg",

With that image_url key there I can render the image by using meal.image_url as the src value for an img tag.

function Meal({ meal }) {
    return (
        <div key={meal.id}>
            <h2>{meal.name}</h2>
            <img src={meal.image_url} alt={meal.name} className='image' />
            <p>Date: {new Date(meal.created_at).toLocaleDateString()}</p>
            <p>Calories: {meal.calories}</p>
        </div>
    )
}

export default Meal;