Paperclip alternative that works in Rails 4 and Rails 6 - ruby-on-rails

Background
We have a legacy, monolith Rails 4.2 project. Call it "Classic."
"Classic" uses Paperclip for dealing with the storage of assets (photos, attachements, etc.) in a 3rd-party system.
We are now breaking the monolith into multiple Rails 6 micro-services. Call it "New".
"Classic" and "New" share the same DB.
The Goal
We want to stop using Paperclip because it is now deprecated and instead use ActiveStorage.
The Problem
"Classic" and "New" are going to have to live at the same time for a while.
"New" cannot use Paperclip because it's deprecated.
"Classic" cannot use ActiveStorage because it requires at least Rails 5.2. ("Classic" cannot realistically be uplifted to 5.2)
So we're stuck in a situation where "New" can use the newer technology, but it requires a DB change that "Classic" will not be compatible with and they share the same DB and "Classic" cannot use the newer technology.
The Question
Has anyone else ever had to deal with a situation like this and was able to find an approach that worked? Or maybe someone has another idea?
My (potentially bad) Idea
Maybe we could go ahead and implement everything in "New" to use ActiveStorage. We could provide some RESTful endpoints that would allow for the retrieval/create/update/destroy of 3rd-party hosted assets.
Then in "Classic", anywhere that Paperclip is used, we could just replace that logic and have it make RESTful calls out to our new endpoints in "New". Along with that, we'd also have to devise a strategy for identifying every asset that was managed via Paperclip and then create equivalent records over in the new ActiveStorage schema. That way the logic in "New" would be able to access past assets. I'm not sure if that is a realistic approach and it probably has some serious shortcomings that I'm overlooking, but it was a thought.

Using this Paperclip to ActiveStorage Migration doc as a guide, I was able to workout a successful solution.
Here is what I did.
In New I built the migration to add the 2 ActiveStorage tables.
Ran the migration.
Tweaked the ConvertToActiveStorage class (provided in the linked migration doc), to iterate over my "Paperclipized" items and in-turn create equivalent ActiveStorage records. Note :: Rather than run the ConvertToActiveStorage as a migration, I set it up to be ran as a Rake task.
Executed the ConvertToActiveStorage logic in Classic.
Updated the models in New to begin working with ActiveStorage objects (rather than the old "Paperclipized" objects). Basically changed from something like :: has_one :photo, dependent: :destroy to has_one_attached :photo
In Classic, added after_create, after_update and after_destroy callbacks on the Paperclipized models. Those callbacks are responsible for creating/updating/destroying the equivalent ActiveStorage records. This allows for Classic ('Paperclipized') and New ('ActiveStorage structure') to exist for a while longer at the same time. So when someone goes into Classic and adds a new user image, the callback will handle also going and creating the equivalent ActiveStorage records so that New will be able to interact with the user image. Same idea for the update and delete. Eventually, when New has the ability to create/update/destroy images, then I'll need to provide callbacks to go the other way (ActiveStorage to Paperclip) but that's a battle for a future day.

Related

Rails how to version the models?

I have created rails application using rails 5 and ruby 2.3. I want to add few changes in some model files and save it as new application. I want two run both applications in production as two similar applications. I know how to version the controller by using namespace but I didn't get solution for version rails models.
Are you saying you want to run two copies of the application in production under different namespaces (e.g., /v1/ and /v2/), both sharing the same database?
There's no easy way, because it's not a good idea. Each time you change to one application, you'll have to remember to change both - and worse if you add more versions in the future.
If possible, you should share one codebase and put any versioning logic in the controller that accesses the model. Your controller can inspect the route that was used to hit it (/v1/ or /v2/) and respond accordingly.
In order to share one database between two versions of your code, you'll need to ensure that any changes you make to the database schema are backwards- and forwards-compatible, meaning you can only add new tables or columns (never rename or remove).
If you don't need the two versions of your app to share the same database, you could give the new version its own copy of the database (database: my_schema_v2 in config/database.yml).
Or you could give the v2 of your model its own table name:
class Product < ApplicationRecord
self.table_name = "products_v2"
end
But again, that means products would not be shared between the old and new versions of your code.

How to use rails generators without active record

I am building a rails app without a database.
In Disable ActiveRecord for Rails 4 I learned how to configure the app so that the absence of the database related gems does not interfere with running it. The problem is that I still want to create models using the commend rails generate mode MyModel.
Under this configuration, the above command does nothing at all.
I am assuming here I would need to require some modules (for example, activemodel, which seems to provide ActiveRecord-like capabilities without necessarily having a DB) in application.rb, but I can seem to find which.
Could someone help?
Thanks in advance
You can take a look at How to create custom generators for my rails app.
Basically you will have to change the behavior of your model generators. This way you can tell which file will be created, with which code template and etc.

Challenge: best way to handle devise user/profile/account removal from database?

Challenge (deactivating):
Having a social media type application in Rails 4 using Devise.
Im looking for a good way to delete users from the system. Since Facebook allows users to reactivate there accounts I have now implemented this feature with a
user.deactivated
boolean, thus not actually deleting records from the database.
Delete users that have not confirmed
Using a rake task I now be able to filter all users that have not confirmed there account for 7 days, These users I would like to really remove from the database. Since users cannot do anything on my application without confirming, I consider them "lost" registrations.
I wonder what would be the best strategy to delete these users in a real removal from the database?
Since each user has its own profile/account what would be the best way to delete everything from the system? Im using:
belongs_to :user, :dependent => :destroy
In profile/account models so the deleting the user from database should delete all there other relations. Is this best way to go? Any things to take into consideration that would break the system?
As You already started going this putting dependencies between models is a good approach, this will remove everything linked on destroy (don't mix up with delete it executes SQL query without Rails callbacks). Though I started to avoid this technique in latest projects.
I am kind of old school about DB cleanliness, so we usually also use gems to enforce FK integrity. It adds another layer of maintenance when deleting objects (as it will throw SQL error when triggered), but I find it more reliable, then a single line in a model. Also we usually overcome this added complexity by providing models with custom_destroy methods that executes delete on all dependent objects.
def custom_destroy
self.class.transaction do
child_objects.delete_all
some_other_child_objects.delete_all
delete
end
end
This provides an advantage of speed, because using destroy_all on 10 child objects will execute 10 SQL queries (+ all the callbacks), this method on the other hand will do only one query.
We seldomly have a need to clean up old unneeded data (expired items), we use a rake tasks for that. If You are on Heroku simply use Heroku scheduler, if You are on a VPS/dedicated machine I advice on using whenever gem, it should have a good integration with Capistrano as well.

Add a file to a database in a Ruby on Rails application?

I've only just started learning ruby on rails and I would like to create an application that will allow me to add files to the database. Currently, I'm developing the rails application using the Aptana plugin for Eclipse and the application is using the default sqllite db.
I have tried generating a scaffold with the following parameters: documents title:string file:varbinary. Then I do a 'rake'->'db'->'migrate'. When I migrate to localhost/documents and click on 'New Document' the application fails and displays an error.
What I would like to do is click on 'New Document', have a field that will allow me to browse for a document on my local computer, select the document and add it to the db on the rails application.
Paperclip is more recommended than attachment_fu these days. It is really simple and easy to use with your active record model.
Is it a particular kind of file you want to add?
I just ask because if it's not data of a kind that benefits from being in a database ( textual data might be searchable, binary data is not ) then you are much better storing it in the filesystem and serving it up straight - especially for stuff like images or video - rather than inserting it into a database and having to go through your application every time a user requests it.
I'm not saying that there aren't any reasons you might want to have a file in the database, but I treat that as a last resort and in ten years of web programming I've not come across a case where it was necessary.
I would highly recommend the attachment_fu plugin as this lets you create models with attachments pretty nicely, Paperclip plugin is another good one also!
If you have trouble deciding which one to use, as far as i can remember, Paperclip makes it easier for multiple attachments, such as an Album has many Photos, and Attachment_fu is easier for single attachments such as a User has one display picture.
We do something like this on a site I'm managing. Instead of storing these files in a database, I'd agree with the other posters here and recommend you try something like Paperclip.
One caveat: if you want access control, make sure that paperclip doesn't save your files somewhere under /public, where anyone could possibly access them if they knew the URL. Deliver files to the user via send_file in your controller.

What's the best way to store the ActiveRecord Models with Versions and their Associations with Versions?

If all I have is one model (for example Wiki) and want to save it along with its versions, I could use acts_as_versioned plugin which stores the wikis in "wikis" table and its versions in "wikis_versions" table. This is plain an simple even if I want to moderate the latest version before showing it to the public using a field as status with "pending review/ published".
What's the best way to handle Wiki with associations (for example attachments, assets,..) which also have versions? And how would you moderate it? Do you create a new version to wiki even though only its association is changed just to keep the flow going, if so what about other associations?
What's the best way to handle it with little db overhead?
Thanks in advance.
I have used both acts_as_versioned and acts_as_audited.
I prefer the latter because it uses a single table. Using acts_as_versioned we've had issues with changes to versioned tables requiring extra migrations => this adds extra complexity to our build and deployment process.
Richard Livsey has a nice plugin for this that works with acts_as_versioned.
http://github.com/rlivsey/acts_as_versioned_association/tree/master

Resources