One of the reasons I like to play with other languages from time to time is to find new ways of solving problems. This is why, while learning Ruby on Rails, I am making a conscious effort to do things the Ruby way, as opposed to writing C# style code in Ruby.
While working on my new blog page, I encountered a problem that was identical to one I had on a previous project but using ASP.Net MVC. In both projects (but for different reasons) I made a design decision to use Amazon’s S3 web service for storing images. However, I wanted the option of switching to an alternative local (file system based) implementation at configuration/deploy time.
The ASP.Net MVC way
The C# solution to this problem was obvious to me at the time, because I was already using an Inversion of Control container. I created two implementations of my image repository and set up the IoC container to use whatever implementation I wanted based on some configuration setting (note: In this extract I am also setting up local/remote message queue implementations):
if (bool.Parse(ConfigurationManager.AppSettings["RunLocal"]))
{
_container.RegisterType<IBinaryRepository, Local.BinaryRepository>(
new InjectionConstructor("F:/TadmapLocalData/LocalBinaryFolder")
);
_container.RegisterType<IMessageQueue, Local.MessageQueue>(
"Image",
new InjectionConstructor("F:/TadmapLocalData/LocalImageMessageFolder")
);
_container.RegisterType<IMessageQueue, Local.MessageQueue>(
"Complete",
new InjectionConstructor("F:/TadmapLocalData/LocalCompleteMessageFolder")
);
_container.RegisterType<FileUploaderAdapter, LocalUploadAdapter>();
}
else
{
_container.RegisterType<IBinaryRepository, Amazon.BinaryRepository>(
new InjectionConstructor(
ConfigurationManager.AppSettings["S3AccessKey"],
ConfigurationManager.AppSettings["S3SecretAccessKey"],
ConfigurationManager.AppSettings["S3BucketName"]
)
);
_container.RegisterType<IMessageQueue, Amazon.MessageQueue>(
"Complete",
new InjectionConstructor(ConfigurationManager.AppSettings["CompleteMessageQueue"])
);
_container.RegisterType<IMessageQueue, Amazon.MessageQueue>(
"Image",
new InjectionConstructor(ConfigurationManager.AppSettings["ImageMessageQueue"])
);
_container.RegisterType<FileUploaderAdapter, DirectAmazonUploader>(
new InjectionProperty("BucketName", ConfigurationManager.AppSettings["S3BucketName"]),
new InjectionProperty("AccessKey", ConfigurationManager.AppSettings["S3AccessKey"]),
new InjectionProperty("SecretKey", ConfigurationManager.AppSettings["S3SecretAccessKey"]),
new InjectionProperty("FileAccess", com.flajaxian.FileAccess.Private)
);
}
The Ruby on Rails way?
While looking for an IoC container or equivalent Dependency Injection framework, I started to realise that although DI frameworks exist for Ruby they are normally not used. One of the articles I read suggested reopening the class and mixing in a module. So I gave this a try.
Most of my models are including a module to define the persistence implementation, for example my Post class is stored as as mongoDB document:
class Post include MongoMapper::Document # ... end
So my first step was to move all image persistence code into two different modules, one for S3 storage and one for local file storage.
Then inside an initializer file, I check my configuration and based on this, I reopen the class and include the appropriate persistence module:
require 'image'
IMAGE_STORE_CONFIG = YAML.load_file("#{RAILS_ROOT}/config/image_store.yml")[RAILS_ENV]
if IMAGE_STORE_CONFIG['use_s3']
class Image
include S3ImageStore
end
else
class Image
include FileImageStore
end
end
This solution works for now, although I am not entirely happy with it. My problem is that my Image model now looks like the following:
class Image attr :name end
This is very simple (which is good) and there are no details about it’s persistence (which is also good), but it doesn’t give the reader any clue that it can persist itself. For example, in C# I would have to have a placeholder (interface) for my implementation that would indicate to the reader that there is some stuff missing and this is what the missing stuff would look like if you want to go looking for it.
I am happy now that I can easily switch between cloud services and local implementations, but I can’t help thinking there is a better way of achieving this…



These common settings are now ready to be used by any project in the solution by following these steps:



