Chapter II - Image Management Engine (Cloudinary)
(avr. time for this chapter: 3 days)
In this chapter, you will build a mountable engine to manage images stored in Cloudinary. Unlike the webhooks engine, this one has its own views, and also exposes a JSON API with the same operations.
Continue building upon the application from Chapter I and the image management features from Chapter IV of the base onboarding (user profile images and ebook covers).
Reference: Cloudinary Ruby on Rails SDK
Exercise 1: Image Library
Requirements
- The engine is mountable, has its own layout, and is mounted at
/media - The
cloudinarygem is a dependency of the engine, not of the host - Images are stored in Cloudinary; their metadata is stored in the shared database:
- Cloudinary
public_id(unique), title, description, folder (e.g.,covers,avatars) - Format, width, height, size, and delivery URL
- Only
jpg,png, andwebpare allowed - All Cloudinary calls go through a single service, used by both the views and the API
- Cloudinary and the database stay consistent:
- If the upload fails, no record is created
- If the record fails to be saved, the uploaded image is removed from Cloudinary
Note: Database transactions do not cover external services. Discuss your approach with your tutor.
Exercise 2: Views
Requirements
- Index - grid of thumbnails with title, folder, and dimensions, with pagination, folder filter, and title search
- Show - the image, its metadata, and the gallery of variants (see Exercise 4)
- New - upload form with title, description, and folder
- Edit - update title, description, folder, or replace the file
- Delete - with confirmation, removes the image from Cloudinary and the database
- Thumbnails are generated by Cloudinary transformations, never the original image
- Flash messages for successful and failed operations
- Only signed-in sellers can access the library, reusing the host authentication without hard-coding host classes
- (Optional) Upload directly from the browser to Cloudinary with a signed upload
Exercise 3: API
Requirements
GET /media/api/images- list images (with pagination and filters)GET /media/api/images/:id- show an imagePOST /media/api/images- upload an image (multipart/form-data)PATCH /media/api/images/:id- update an imageDELETE /media/api/images/:id- delete an image- Token authentication (an API token per user)
- Consistent JSON: same serialization in every endpoint,
422for validation errors,404for missing images - No logic duplicated between the API and the HTML controllers
- Endpoints documented in the engine README with request and response examples
Exercise 4: Variants
Cloudinary transforms images on the fly just by changing the delivery URL. Instead of letting every part of the application build its own transformations, the engine offers a catalog of ready-made variants. Users see each image already prepared in every variant and choose the one they want to use.
Suggested Variants
thumbnail- small square crop for lists (e.g., 150x150)avatar- circular crop focused on the face (e.g., 200x200)cover- ebook cover proportion (e.g., 600x900)banner- wide image for headers (e.g., 1200x400)grayscale- any of the above with the grayscale effect
Requirements
- Variants are defined in a single place in the engine (name, description, transformation)
- The host can add or override variants through configuration
- Unknown variants raise an error
- Variants are always delivered with automatic format and quality
- The show page displays the image in every variant (name, dimensions, URL, "Copy URL" button)
- "Use this variant" lets the user pick an image and a variant for a purpose (e.g., ebook cover, profile image); the choice is stored with a polymorphic reference to the record using it
- API endpoints:
GET /media/api/variants- available variantsGET /media/api/images/:id/variants- URLs of an image in every variantPOST /media/api/images/:id/usages- choose a variant of an image for a record- (Optional) Variants are generated at upload time with eager transformations in a Sidekiq job
- (Optional) Users can build a custom variant (width, height, crop, effect), preview it, and save it
Reference: Cloudinary Image Transformations
Exercise 5: Background Jobs and Integration
Requirements
- Deleting an image from Cloudinary runs in a Sidekiq job
- A daily job compares the database with Cloudinary and reports images that exist only in one of them
- Sellers pick ebook covers from the library (image +
covervariant) instead of uploading a new file - Users pick their profile image the same way (
avatarvariant) - Images in use (covers, profile images) cannot be deleted
- (Optional) Cloudinary upload notifications are received through the webhooks engine from Chapter I
Exercise 6: Tests
Requirements
- Tests never call Cloudinary (stub the service or use WebMock)
- Model specs, including
variant_urlwith unknown variants and variants overridden by the host - Service specs, including failure scenarios
- Request specs for every API endpoint, including authentication failures
- Specs for the HTML pages (index, upload, edit, delete, choose variant)
- Job specs
- The engine's suite runs in your CI pipeline
Checklist
Before finishing this chapter, ensure you can:
- [ ] Build an engine with its own views, layout, and JSON API
- [ ] Integrate an external service through a service layer
- [ ] Keep an external service and the database consistent when failures happen
- [ ] Deliver optimized images with transformations
- [ ] Define reusable image variants and let the host configure them
- [ ] Reuse the host authentication inside an engine
- [ ] Test an engine without calling external services