Store a list of .rb files inside model - ruby-on-rails

colleagues! I have Document model. Document should be processed by one of the parsers (in my project they are called 'importers' and stored inside 'lib/importers' folder). The question is about what is the best way to implement entity Importer inside models layer? (for instance associate document with importer).
First idea is to create importers table, but then I will have 2 independent places where importer names will be saved (database and file system). Bad cases:
Case 1: I've aded a new importer, but forgot to add it to importers table = I can't associate document with this impoter
Case 2: Importer was renamed and we forgot to rename it inside database = error
I decided to define
def Document.importers
#importers ||= Dir.entries("#{Rails.root}/lib/importers/")
.select { |name| !File.directory?(name) && name != 'base_importer.rb'}
.map { |name| name.gsub(/\.rb$/, '') }
end
for f.association inout and add importer string attribute to the document model. So I can get importer class in following way -- 'importer.classify.constantize'. It works, but it looks creepy
Can you advice better solution for this situation? I will appreciate to hear any ideas ;)

I would model them in the database, and then have some system in place to update the database if the files change. This happens all the time with asset management systems: you have some physical files, and some data, and yes, if they go out of sync you've got problems. So, you put systems in place to try to keep them in sync. You can have a rake task to update the database based on the physical files for example, then the protocol is that you need to run the rake task after changing the files, and if you don't then you screwed up and the problems are your fault. Developers should be able to work within these sorts of rules.
Ultimately, Rails is about object-relational data, so work with that if you want to use rails. If you try to go down some route of building a load of instances on the fly every time, based on the contents of the folder, then you will just end up with a very complicated and inefficient system.

Solvation made during brain storm in my local ruby chat:
I haven't mentioned that all importers are inherited from the base_importer which is also placed in lib/importers. We decided to add 'inheritors' array to it and store all inheritors there using hook 'inherited' provided by the ruby core -> http://ruby-doc.org/core-2.2.0/Class.html#method-i-inherited.
class BaseImporter
#inheritors = []
def self.inheritors
#inheritors
end
def self.inherited(subclass)
#inheritors << subclass.name
end
# ...
end
We expected it to work well, but we forgot, that all classes are eager loaded in Rails. So BaseImporter.inheritors will return [] to you on the fresh system start.:
anton#anton:~/Projects/project$ rails c
Running via Spring preloader in process 16832
Loading development environment (Rails 4.2.5)
B2.2.3 :001 > BaseImporter.inheritors
=> []
To force loading of all importers I made an initializer:
# config/initializers/importers.rb
# Preload importers
require 'base_importer'
Dir.glob("#{Rails.root}/lib/importers/*.rb").each { |file| require file }
BaseImporter should be loaded first, because if any importer will be loaded before BaseImporter it will not fire 'inherited' hook:
anton#anton:~/Projects/project$ rails c
Running via Spring preloader in process 16846
Loading development environment (Rails 4.2.5)
2.2.3 :001 > require "xxx_importer"
=> true
2.2.3 :002 > require "base_importer"
=> true
2.2.3 :003 > require "yyy_importer"
=> true
2.2.3 :004 > BaseImporter.inheritors
=> ["XxxImporter"]
Feel free to post your feedback on this solution. I will be glad to hear it

Related

Override master db selection for splitting read write queries in rails

To distribute load and improve the database performance, I am trying to split read-write queries with some custom modification. I am using makara(https://github.com/instacart/makara) gem for the same in my Rails application. The same is working fine for splitting read-write queries to master and slave. I need to modify the master db selection by checking the replication lag with some custom condition.
I have defined a custom proxy class(as mentioned in documentation) in config/initializers directory but same does not seem to work.
class MyAwesomeSqlProxy < ::Makara::Proxy
hijack_method :select, :ping
send_to_all :connect, :reconnect, :disconnect, :clear_cache
def connection_for(config)
::Sql::Client.new(config)
end
def needs_master?(method_name, args)
return false if args.empty?
sql = args.first
sql !~ /^select/i
end
end
When I make any changes in needs_master? method, same is not reflecting while running the query. I am not sure if I am defining it at the wrong place or this class needs to be loaded somewhere explicitly.

Rails 6: How to load global variables only once?

My app uses global variables like logos, app name, etc retrieved from the database and shown on different controllers and views. I put it in ApplicationController to be available to all, but I find that the individual controllers repeat the same query sometimes.
class ApplicationController < ActionController::Base
$image = Setting.find_by_name('image').value
$city = Setting.find_by_name('city').value
$currency = Setting.find_by_name('currency').value
end
Is there a way to make the same variables available to all controllers (and users) with just a one-time query with the variables saved on memory, such as when the app starts up?
You could attempt to use initializers.
Rail will load all files in config/initializers/ folder when the server starts up. This can work as a place to initialize application-wide variables. We could create a file inventory.rb file in the initializers directory:
at config/initializers/inventory.rb
module Inventory
class Count
Orders = Order.all
end
end
Inventory::Count::Orders
# => "[your orders will show here]"
These will only be loaded once when the server is started or restarted. As such this works well if the values you need won't change. If they will change I don't think there is a good way to avoid running multiple queries.
Whats about caching? Rails is using SQL Caching and you can use Low Level Caching. See the guides (1.7 and 1.8): https://guides.rubyonrails.org/caching_with_rails.html#low-level-caching

Where to put scratch file to be able to access Model data/methods

I have a file that I use for testing ideas and ensuring a code refactor is working. I'd like to be able to access my Shop Model data from within this scratch file. For instance I'd like to be able to do this in the tester.rb file:
pp Shop.all
Where would I put it, and what should it inherit from in order for it to work? I've tried the following with no success.
I put a tester.rb file in the models folder and tried inheriting from:
class Tester < Shop
class Tester < ActiveRecord::Base
And yes I can use pry or irb but my testing involves more than a couple lines of code, and a console gets messy fast.
Thanks
One approach is to use Pry's play command.
What I do for testing things out or writing one-off scripts is:
Create a a folder in my project root called rcs (short for "Rails Console Scripts")
Add that path to .gitignore so it won't be added to version control
Add test code to a well-named file in /rcs and play it back in the Pry / Rails Console session as needed
Protip: The first line of your RCS script should always be reload! so that when you play it back you reload the console and get a fresh play of your script.
Example rcs script:
# /rcs/bm.rb
reload!
Benchmark.bmbm { |x|
iterations = 10_000_000
x.report("double quotes") {
iterations.times do |i|
"hi"
end
}
x.report("single quotes") {
iterations.times do |i|
'hi'
end
}
}
Example .gitignore entry:
rcs/
Example invocation in Rails Console:
play rcs/bm.rb
In your case, you might want to make /rcs/shop_test.rb and just monkey patch your Shop class. For example:
reload!
class Shop
def my_method
# Do something special
end
end
shop = Shop.new
shop.my_method
Then just call play rcs/shop_test.rb from the Rails console each time you make an edit to /rcs/shopt_test.rb to see the new output/results.

Loading class descendants in rails development

I need to be able to see all of the class descendants from a controller when I go into the rails console locally. I have this Api::BaseController which all my Api controller inherit from. The issue I have is when I hop in to the rails console to check which Api controller are in the descendants, it comes up empty until I call them. This probably has something to do with how the classes in development aren't eager loaded, and they're not cached locally.
sample_app$ rails c
Loading development environment (Rails 4.2.0)
2.1.5 :001 > Api::BaseController.descendants
=> []
2.1.5 :002 > Api::V1::FoosController
=> Api::V1::FoosController
2.1.5 :003 > Api::BaseController.descendants
=> [Api::V1::FoosController]
From this example, you can see when I call descendants on the Api::BaseController the first time, it's an empty array. After calling one of the controllers that class will be then loaded and will show up as a descendant. In this case, there could be an any number of controllers in V1 as well as V2, V3, etc...
As a stupid ugly hack, I could do
Dir.glob("#{Rails.root.join('app', 'controllers', 'api', 'v1')}/**/*.rb").each(&method(:require_dependency))
but I don't want to have to write that each time I enter the console. I'm also working on a gem, and definitely don't want to put this sort of code in my gem.
The other option is caching classes in development, but that causes a huge issues on it's own. Anyone have any ideas?
Edit
Another option would be to call Rails.application.eager_load!. This option would work fine if I could specify only controllers in my API folder. Then I wouldn't have to eager load the entire app, but just a small subset of controllers that I need.
I found the following post: http://avinmathew.com/using-rails-descendants-method-in-development/
In short it says the following:
In enviroments/development.rb add the following:
config.eager_load_paths += Dir['path/to/files/*.rb']
ActionDispatch::Reloader.to_prepare do
Dir['path/to/files/*.rb'].each {|file| require_dependency file}
end
The first line adds the path that should be loaded when you start your app (or console)
and the rest tells rails to reload the classes on each request.
Works in Rails 5 & Rails 6:
In environments/development.rb:
Rails.application.reloader.to_prepare do
Dir["#{Rails.root}/app/models/my_models/*.rb"].each { |file| require_dependency file }
end
This builds on u/Aguardientico's answer above.
If requiring files not in config.eager_load_paths you may need to add yours as described above.
ActionDispatch::Reloader is replaced with Rails.application.reloader (thanks to u/jean-baptiste's comment).
#{Rails.root} was necessary as part of my argument to Dir for this to work.

How to implement generators for a plugin located at the `lib/<plugin_name>` directory?

I am using Ruby on Rails 3.2.2. I have implemented a Something plugin (it is almost a gem, but is not a gem) and all related files are in the lib/something directory. Since I would like to automate code generation related to that plugin, I came up with Ruby on Rails Generators. So, for the Something plugin, I am looking for implementing my own generators in the lib/something directory.
How should I make that and what are prescriptions? That is, for example, what rails generate command line should be invoked to properly generate all needed files in the lib/something directory? generators would still work with plugins (not gem)? what are advices about this matter?
I would make it a gem. I've made generators using gems, but I don't know if the generators would still work with plugins.
If you are having difficulty with the command line, I am guessing that you don't need any argument. (If you need an argument, I could copy the provided templates, and if I needed some other argument I'd be lost, so my advise is limited to non-argument.)
I have a generator gem which generates migration files needed for another gem. It checks if the migration with a given root name (w/o the timestamp prefix) is in db/migrate, and otherwise creates it.
Here is my code. I think this example is the help you need.
class ItrcClientFilesGenerator < Rails::Generators::Base
source_root(File.dirname(__FILE__) + "/../src")
desc "Generator to create migrations for needed db tables"
def create_itrc_client_files
prefix = DateTime.now.strftime("%Y%m%d%H%M")
existing_migrations =
Dir.glob("db/migrate/*itrc*").map do |path|
File.basename(path).gsub(/^\d*_/, '')
end
Dir.glob(File.dirname(__FILE__) + "/../src/*").sort.each_with_index do |src_filepath, index|
src_filename = File.basename(src_filepath)
unless existing_migrations.include?(src_filename.gsub(/^\d*_/, '')) then
this_prefix = "#{prefix}#{'%02i' % index}_"
dst_filename = src_filename.gsub(/^\d*_/, this_prefix)
copy_file(src_filename, "db/migrate/" + dst_filename)
end
end
end
end

Resources