Do not nest files in rails active storage - ruby-on-rails

It appears that by default, Rails Active Storage nests your file uploads by means of the associated active_storage_blob key.
The rules appear to be as follows for the default behavior. Within the <Rails.root>/storage/ directory:
take the first two characters of the key and make a directory
within that directory, take the next two characters of the key and make another directory
store the file there, and the file name is the entire key
For example: where the key of a particular file's associated active_storage_blob is: 2HadGpe3G4r5ygdgdfh5534346, It would look like the following:
I do not want this nesting behavior. I want to store the files flat within the storage directory. So I simply want it to look like this:
.
How can I do that? A google search and a read through of the Active Storage Rails Guides didn't reveal a solution.
Also just out of curiosity: why is this the default behavior?

Digging around in the code of the ActiveStorage DiskService, I found the code which generates the folder structure. All is conveniently contained within a single function:
def folder_for(key)
[ key[0..1], key[2..3] ].join("/")
end
This makes it easy to eliminate the two-letter subfolder structure by a simple patch:
module ActiveStorage
class Service::DiskService < Service
private
def folder_for(key)
""
end
end
end
Best to do a little testing on this patch, but as far as I could tell it should work just fine.
The answer to the second question I was not able to determine by just looking at the DiskService code. There are no clues there to this folder structure, so the reasons may lie elsewhere. It may be done entirely for cosmetic purposes, in order to avoid a giant single folder blob on large servers. Perhaps someone who knows more can comment.

Related

Where to store text files in a Ruby on Rails directory?

I have a text file in my Ruby on Rails application that I would like to call in a model. The problem is, I want to follow rails conventions in terms of placing files.
The reason I'm using a .txt file is because I have a very large array of words I have to iterate over. This is for a model validation and I need to ensure that the input does not contain any of these words.
I could just declare an array, but it would make my models to fat. Therefore, I'd like to read from a file where I place my comma separated array elements.
There are several ideas I have
Root Directory: I could add the .txt file to my root directory directly
Public Directory: Could store it in /public
Temp Directory: Could store it in /tmp
Lib Directory: Could store it in /lib
Inside Model: Could just store it inside /app/models/mymodel.rb
Tried to find a similar answer here, but couldn't find a consensus. Anyways, this is a two part question. How would I read from the file inside the model. Would I do something like
file = File.read("#{Rails.root}/public/textfile.txt")
Thanks for your help
There are no conventions for storing files. All files can be stored in the public folder. This can vary if you're using Heroku or hostings that do not allow write access on the file system, and obviously you need to write.
Otherwise, you must take into account what is the purpose of this file. It might be a better idea to store files in a database or using rails store systems that are explained here: session store guides and here you can find more information about security
I hope you are not opening and reading this txt file each time you do a validation. I would suggest writing a rake task that would read the file once, parse the words and store them in a an indexed database column. You could create a model ForbiddenWord with an attribute called 'name' of type string(with index of course). Then you could write a custom validator and use it in MyModel, something like this:
class WordConstraintValidator < ActiveModel::Validator
def validate(record)
if ForbiddenWord.exists?(name: record.name)
record.errors[:base] << "You can not use the word #{record.name}"
end
end
end
class MyModel < ActiveRecord::Base
validates_with WordConstraintValidator
end
You can put your custom validator inside /app/models/, I am also assuming you have a name attribute on MyModel class, otherwise change this accordingly. There could be other possible solutions to this problem, this is the first thing that comes to mind.

Storing and retrieving static data in rails

In a Ruby on Rails 3.2.12 project I want to store data for a model in static files.
I don't need to be able to write to the data through the app, and want to handle updating and adding new objects by changing the static file(s).
Is there a way to set up a model, and then say "look at this file (or folder) to find the records", rather than the database (the app also has a database)?
I can imagine the data being stored in two main ways:
all the records are kept in a .json or .yml file
each record is kept in an individual .yml or .markdown file, inside a specific folder (as in jekyll).
Examples: a glossary
My application has a glossary. Each entry in the glossary should be an individual record.
There will be a glossary index page (/glossary) that is just like your standard glossary, listing all the terms and definitions. Each individual record(term + definition) also needs to be individually retrievable for use in footnotes on other pages.
I want people to be able to edit the glossary by editing files through github. They could be pointed to a single file with all the records, or (preferably) an individual file with just the record they want to edit (less syntax to handle, less risk).
Key questions
How do you point the model to the static data file, rather than the database?
Where should the source directory be?
What would be the best storage format?
Thanks very much for your help!
ActiveHash would be a good solution for this. You could use the ActiveYaml configuration if you would like to store the data in a separate yaml file instead of directly in the model.
Unless someone comes up with a better solution or with a gem which can do this out of the box I would recommend building your own model with own find and save methods. Something like (untested):
class Term
attr_accessor :name, :definition
def self.find(name)
YAML.load(File.read("#{name}.yaml"))
end
def save
File.open("#{name}.yaml", 'w'){ |f| f.write(YAML.dump(self)) }
end
end
The format for saving is up to you, see here or here for more info. Since you want the users to change the files I would go with whichever you find the most user friendly.

Models have access to text files in Rails?

As a learning exercise, I'm trying to convert an existing Sinatra app to a Rails app. The information in countries.txt will eventually be moved into a database, but in order to keep things simple for me, I'd like to first read data in from the text file, in the same way the source app did. Problem is I can't figure out where File will read from in a Rails app. Where in the Rails directory do I put the countries.txt document for a method in a model to have access?
def get_random
content = File.read("countries.txt")
words = content.split("\n")
words[rand(words.size)].upcase
end
I don't have a good suggestion on where to put countries.txt, but let's say you put it in the 'config' directory. You could then use the following to read it, regardless of what file is doing the reading.
content = File.read(File.join(RAILS_ROOT, 'config', 'countries.txt'))
However, if you don't want them in the database, there aren't that many countries... I would consider creating a file in say config/initializers/countries.rb that had something like this:
COUNTRIES = ['Country 1', 'Country2', etc...]
Or a hash mapping the name to the iso code. The advantage to this is you're only reading the file once, not every time you need to get a random country.
But with all that said.. you could also use one of the country gems that are out there to deal with it for you.

What command can I use to create this Ruby on Rails model?

I'm creating a model in Ruby on Rails that will act like a file system. You'll have assets (like files) that could either be folders or files themselves. How can I create a command for this?
Asset
id (unique auto-incrementing number)
name
is_directory (bool)
user_id (id of the owner)
parent_asset_id (id of parent directory, or null if under the root)
access_token (randomly generated token, used to send shareable links)
contents
I'm thinking something like:
rails generate model Asset
name:string
is_directory:boolean
user_id:integer
parent_asset_id:integer
access_token:string contents:??
Some questions I have:
What's the difference betweeen blob vs longblob vs mediumblob vs longtext vs etc, and which would I want to use? (The assets are essentially text... not sure what the max size will be yet)
Is the parent_asset_id a good naming convention, or is there something else that would make Rails give me some secret sauce, similar to why I picked the name user_id (to match the User model)?
Is there a way to declare a default random string value for the access_token? (The access token will be used for a shareable link to the asset)
Anything else I'm overlooking?
This is a detailed question, so I hope it serves as a case study for anyone looking to implement something like a file system in RoR.
Obviously if you really wanted to implement a file system you'd use an actual file system or Amazon S3... but if you want a light-weight file-like system in RoR, this seems like the best approach.
First off, I strongly recommend you consider an existing gem like Paperclip, which will handle a lot of these details for you.
Answers in order:
You would use the binary field type to store general data, but if you use Paperclip there are some specific fields you would need to use instead, which are explained in Paperclip's docs.
If by parent_asset_id you really mean the asset can 'belong' to many other models, then look into setting up a polymorphic relationship, with an id and type field. If instead you mean storing the path to the stored file, then Paperclip handles this for you. See #3 for details...
You can access a stored file on Paperclip by calling something as simple as asset.url in your view. If you wish to go manual and insert a random code, you can insert a callback into your Asset.rb model that does something like:
before_create :generate_key
def generate_key
self.key = ActiveSupport::SecureRandom.hex
end
S3 is not a complex system to set up on Rails, and it is far more flexible and scaleable than storing the files elsewhere - however, if you want to, then use the 'assets' path.

Where is the best place to store application parameters : database, file, code...?

I am developing a Ruby on Rails website and I have an "architectural" question : my application needs some parameters and I'm wondering where to store them.
In concrete terms, my application receive some requests which are evaluated and then sent. So, the Request model must have attributes concerning these treatments : a validation status and a sending status. For instance, validation status can be "accepted", "rejected" or "waiting". Sending status can be "sent", "waiting", "error during sending" or stuff like that. I have to store those status codes parameters somewhere, but I don't know what is the best solution.
I could create a model for each one and store them in the database (and having an active record model ValidationStatus for instance) but : wouldn't it be a bite excessive to create a database/model for storing data like that?
I could also just use them in the code without "storing" them, I could store them in a YAML file...
So, a more simpler question: how do you deal with your application parameters in RoR?
There are lots of global configuration plugins, most of them revolve around the idea of loading a YAML file at some point. Check this page, this plugin and even this Railscast.
I put them in the database. I have a lot of these, and they are all pretty straightforward lists of strings. The tables are all the same - id, name, description.
I generate models for them rather than having an actual model file for each one. In app/models I have a file called active_record_enums.rb, which in your case would look something like this:
ACTIVE_RECORD_ENUMS = %w{
ValidationStatus
SendingStatus
}
ACTIVE_RECORD_ENUMS.each do |classname|
eval "class #{classname} < ActiveRecord::Base; end"
classname.constantsize.class_eval do
# Add useful methods - id_for(name) and value_for(id) are handy
end
end
This file has to be required in a config file somewhere; other than that it's pretty straightforward.
(Have since viewed that rails cast mentioned above [episode 85] - it looks like a bit more 'the rails way' than below)
Another approach is to build on the existing configuration mechanism in Rails.
Lets presume there are two types of configs:
App wide configs common to dev/test/prod environments
Configs specific to envrionments dev/test/prod
For the first scenario, items in "RAILS_ROOT + '/config/environment.rb'" work. Just see that the names are captialised so they are Ruby constants. A variation to this is have a reference to another file in that environment.rb file ...
require RAILS_ROOT + '/config/appConfigCommon.rb'
and place relevant config items in that file. This has the advantage of being able to be referenced independant of Rails.
For scenario 2, a similar approach can be taken. Place items for development in "RAILS_ROOT + '/config/environments/development.rb'" or something like
require RAILS_ROOT + '/config/environments/appConfigDev.rb'
and place environment specific items in that required file, making sure they start with caps. And follow the same pattern for test/prod (and others if need be).
The config items are directly accessible in views and controllers (not sure about models) by simply using the constant name.
I am not using Ruby but I will tell you that I started out (in ASP.NET) placing lots of settings in a Web.Config file (similar to a YAML). As time went on, though, the system evolved to the point where different instances needed different settings. So, almost all of them have migrated to the database. So...if you'll be deploying multiple instances of your site, I'd strongly recommend keeping settings in a table of your database (mine has just one record, with fields for various settings). If I had done this to start, I'd have saved a significant amount of time.
I tend to just make a string column for each, and use validates_inclusion_of to set what's acceptable for them.
class Request < ActiveRecord::Base
validates_inclusion_of :validation_status, :in => ['accepted','rejected','waiting']
validates_inclusion_of :sending_status, :in => ['sent','waiting','...']
end
If you need to have things happen (ie. emails sent) when the status changes, look into using the Acts As State Machine plugin to manage it.

Resources