Autoloading from namespace inside a custom folder in app - ruby-on-rails

We're currently developing a custom cms engine for ROR 3.2. In this process several class types originated that we want to be first class citizens in our rails application, meaning they should reside under the app folder of the application and it's plugins.
Currently we're having the following types:
DataSource
DataType
View
I created multible directories under the app folder to hold these:
app/data_source
app/data_type
app/view
More types will follow and I'm a little worried with polluting the app folder with so many directories. Thus i want to move them into a subdirectory/module that holds all Types defined by the cms.
All classes should be inside a MyCms namespace and the directory layout should look like this:
app/my_cms/data_source
app/my_cms/data_type
app/my_cms/view
But now I'm having trouble with autoloading because rails default autoloading would excpect the paths to be like this:
app/data_source/my_cms
app/data_type/my_cms
app/view/my_cms
But this way I would not have grouped all object types in one directory.
What I want is somewhat similar to view grouping of isolated engines. In Devise for example, all views are grouped in the views/devise subdirectory.
Any idea how this could be achieved without to much custom implementation?

You would have to add app/my_cms to your autoload path within config/application.rb:
config.autoload_paths << "#{config.root}/app/my_cms"
provided that your classes are defined without a namespace like this:
class DataSource
...
end
If you namespace them like this in app/my_cms/data_source.rb:
class MyCms::DataSource
...
end
you might add the app folder to the load path:
config.autoload_paths << "#{config.root}/app"
Alternatively, you can do it manually but you lose the reloading for those classes in Rails development:
in app/my_cms.rb (and with autoloading for app in place):
module MyCms
autoload :AnotherDataSource, 'my_cms/data_source/one_data_source'
autoload :AnotherDataSource, 'my_cms/data_source/another_data_source'
...
end

Related

Rails custom project structure autoloading configs

I have a standard app that contains of sub-apps that we will slowly migrate into separate gems. The main problem is that each sub-app basically has an almost identical schema and very similar business logic that sometimes is really pretty much the same.
Currently, as a first step, we created a subfolder in lib/client with a structure like a typical rails app.
As an example, a concern for a ClientA looks like this/
module Client
module ClientA
module Concerns
module MyConcern
...
end
end
end
This all works fine and gets autoloaded by Zeitwerk.
However, when I want to create an initializer in a Rails way, I don't want to wrap it inside modules and make a class around it and this is where I am getting lib/clients/ClientA/config/initializers/custom_initializer.rb to define constant Clients::ClientA::Config::Initializers::CustomInitializer, but didn't (Zeitwerk::NameError)
This folder gets autoloaded like this
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += %W(#{config.root}/lib/client/**/*)
Is there a way how to
Blacklist the folder from being autoloaded by Zeitwerk?
Load it using require or in any other way without wrapping it in modules?
You can ignore parts of the project in zeitwerk using Loader#ignore. You can access rails zeitwerk autoloader via Rails.autoloaders.main.
So you should be able to add something like this to your application.rb:
Rails.autoloaders.main.ignore(Rails.root.join('lib/clients/*/config/initializers/*.rb'))
You'll then have to require them manually, maybe in an initializer in the main app directory. You could create a config/initializers/clients.rb with something like:
Dir[Rails.root.join('lib/clients/*/config/initializers/*.rb')].each do |filename|
require filename
end

Autoloading Rails models that don't follow the default file name structure

THE SHORT VERSION: How do I tell Rails to autoload an ActiveRecord class called ClassName that isn't located in the default location (app/models/class_name.rb). E.g. what if it's in app/models/subdirectory/class_name.rb and I don't want to rename the class to Subdirectory::ClassName?
THE LONGER VERSION:
I know that, by default in Rails, my class names have to follow a specific structure for Rails to be able to autoload them.
E.g. If my class is called Person, if I put it in app/models/person.rb, Rails can load it fine, but if I put it in e.g. app/models/person_class.rb, it can't. If I namespace it, e.g. Humanity::Person, I need to put it in the right folder app/model/humanity/person.
(Plus I can put classes in lib but I'll leave aside that detail for now)
So far so good. But what happens when I have a ton of ActiveRecord classes clogging up my app/models folder and I want to logically organise them into directory, but don't want to rename or namespace the actual classes? How can I tell Rails to autoload this classes?
Or is there a good reason why I shouldn't do this?
Add this line to config/application.rb
config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**/}')]
Then you can use your model with their name as it is without name-spacing.

Same controller name in two different directories

From 'Agile Web Development with Rails 4' pag.272.
If an incoming request has
a controller named (say) admin/book, Rails will look for the controller called
book_controller in the directory app/controllers/admin. That is, the final part of the
controller name will always resolve to a file called name_controller.rb, and any
leading path information will be used to navigate through subdirectories,
starting in the app/controllers directory.
Imagine that our program has two such groups of controllers (say, admin/xxx
and content/xxx) and that both groups define a book controller. Thereā€™d be a
file called book_controller.rb in both the admin and content subdirectories of
app/controllers. Both of these controller files would define a class named BookController. If Rails took no further steps, these two classes would clash.
To deal with this, Rails assumes that controllers in subdirectories of the
directory app/controllers are in Ruby modules named after the subdirectory.
My question is: how could the two book_controller.rb files clash?
I have two different URLs: ..../admin/book and ..../content/book, how can they clash? In the previous paragraph it explicit says
[..] any leading path information will be used to navigate through subdirectories, starting in the app/controllers directory
The file names do not matter. What matter are the constant names.
It's totally okay to have two files with identical names, like controllers/admin/books_controller.rb, and controllers/books_controller.rb.
However, the class names inside those controller should be different. You can add namespace to differentiate them. For example
class Admin::BooksController
class BooksController
Imagine the filepaths like this
../app/controllers/admin/book_controller.rb
&&
../app/controllers/content/book_controller.rb
Rails uses convention over configuration: http://en.wikipedia.org/wiki/Convention_over_configuration
Like it states in the last paragraph
If Rails took no further steps, these two classes would clash. To deal
with this, Rails assumes that controllers in subdirectories of the
directory app/controllers are in Ruby modules named after the
subdirectory.
Someone please correct me if I'm wrong but rails would look for models named admin.rb and content.rb

what is the correct way to add classes to a controller in rails?

If i need to add (project specific) classes to my controler in rails, what is the correct way/place to put and "include" them/there .rb files? (quotes for: not the ruby keyword include)
I am new to rails, and did not find the correct way. LIB sounds like for more public libraries and - what I have learned - is not reloaded per default in dev mode.
sure, I could put all in controler.rb, but ...
the anser for me:
First: there are no rules, if you keep in mind (or learn like me) the rails rules:
NameOfCla -> name_of_cla(.rb) <-- not using class as word for clearence
name your class how you like:
class ExtendCon #<--- not using controller here for clearence
....
put it in a file extend_con.rb, wait for the path explaination, please. if you named your class 'MYGreatThing' it will be 'm_y_great_thing' (never testet that), so avoid chineese charachters
if your controller uses
#letssee=ExtendCon.new
rails learns that class and file (extend_con) on its own. i still did not figure out if a server restart is needed. (the first time)
choose the path to put the file: (I preferre Daves way) app/myexten or what you like, making it 'app' specific and still distquishes to standard rails 'things'
if you are not lasy like me (i put it in app/ontrollers)
put the path you have choosen into
config/application.rb like (comments are there to find it)
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/app/controllers)
config.autoload_paths += %W(#{config.root}/app/myexten)
this one workes for me in all modes including "developer" and i did not need to put "my own" things in app/lib
It depends.
I tend to put library code used explicitly (e.g., instantiated, injected, etc. into app-level artifacts) into app/xxx where xxx signifies the "type" of thing, like decorators, services, etc.
Magic stuff tends to end up in lib, like monkey patches, architectural-level artifacts, and so on.
Code anywhere can be added to the autoload paths, required automatically by an initializer, etc.
Rails 4 comes with an internal directory for controllers called concerns. You could try using that.
app/controlls/concerns
If you have concerns/foo_bar.rb, you include it as follows:
class FooController < ApplicationController
include FooBar
end
Models also have their own concerns directory. I find this approach useful, and it can be applied to Rails 3. You just have to add the directories to your load paths.

Ruby on Rails delay in updating

I'm learning rails and I've come across a little quirk that I can't seem to find the answer to anywhere:
Since I'm learning rails, I'll make a few tweaks to the code while the localhost is running (rails s) and then just refresh the browser to see if the change I wanted to make was accurate. This works for changes to the views, css, html, routing, etc.
But now I'm making changes to a controller file that is calling another ruby class that I wrote and when I make changes to the ruby class, they don't show up right away. The way I know this is that I use a variety of printf functions in the Ruby class to show the current state of things and if I add one and re-run, it won't show unless I shut the server down and restart it.
Any thoughts? Is this a known issue?
You must autoload the folder which contains your custom files:
# in config/application.rb:
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/extras)
There you write the folder which you want to be autoloaded.
WARNING: the naming is very important: files in there must be named as the class/modules they define (like models, controllers, etc):
foo.rb must define Foo costant
foo/bar.rb must define Foo:Bar costant
and you cannot autoload files which do not have this naming convention. The reason is linked to the autoload working: when in your code call f.e. the Foo constant, and the constant is missing, Rails tries to see if in its autoload paths there is a file that follows this naming convention, and if there is it loads it.

Resources