Rails load path questions - ruby-on-rails

Say I have some custom classes that don't belong in models, controllers etc, I should put this in /lib correct?
In rails < 3 I would add this directory to my loadpath and in rails 3+ I would add this to my autoload_path. Is this correct?
Now say I have some classes that extends already defined classes. Where should I put this so its run on startup. Forexample say I want to add the method 'foo' on String.
class String
def foo
'foo;
end
end
Where should I put this class so it's defined on startup?
Another weird bug I have is when I try to namespace classes in lib.
module MyProject
class Foo
end
end
Now in a console:
ruby-1.9.2-p136 :004 > MyProject::Foo
LoadError: Expected /Users/me/workspace/my_project/lib/foo.rb to define Foo
from /Users/rob/.rvm/gems/ruby-1.9.2-p136/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:492:in `load_missing_constant'
from /Users/rob/.rvm/gems/ruby-1.9.2-p136/gems/activesupport-3.0.3/lib/active_support/dependencies.rb:183:in `block in const_missing'
I keep getting this error. How can I load this file?

In rails 3, the autoload path is disabled in the config/application.rb
#config.autoload_paths += %W(#{config.root}/extras)
You have to de-comment this line if you want to load code from lib dir.

You can generally put the class files wherever you want, for example you could put them in app/others and add the directory to your load_path in Rails 2 or autoload_path in Rails 3.
To extend already defined classes you'll probably want to put the files in your config/initializers directory.
To fix the bug you mention you should probably define the Foo class in your foo.rb file, and make sure that the module names match up (Bags and MyProject).
The reason the name was changed to autoload is that the classes in autoload_paths are actually getting autoloaded, not simply loaded. This is the difference between using 'autoload' and 'require' in Ruby.

Related

Rails modules as strict namespaces

I'm quite new to rails and I'm a bit confused of how modules work here. I have a project structure like this:
# app/models/foo.rb
class Foo < ActiveRecord
# lib/external_service/foo.rb
module ExternalService
class Foo
# lib/external_service/bar.rb
module ExternalService
class Bar
attribute :foo, Foo # not the model
I have worked with many coding languages before and I expected it to be easily possible to use 'Foo' inside Bar and ExternalService just like that but
LoadError: Unable to autoload constant Foo, expected lib/external_service/foo.rb to define it
The ExternalService::Foo should normally not even be visible outside of ExternalService but the whole project dies on this thing
Am I just missing a kinda 'strict mode'-notation or anything to make sure that I obviously mean ExternalService::Foo inside the service and prevent the service from killing my model?
I know I can just prepend the module but i wanna keep the code readable.
so you are using rails 4
if you want to create a module, first you need to import or autoload your lib folder
for example in application.rb you can add lib folder to autoload:
config.autoload_paths << Rails.root.join('lib')
after that because you are using rails you should create a folder hierarchy with snake cased name of your module hierarchy
for example if you have:
module ExternalService
class Foo
...
end
end
your foo.rb file should be in a folder with name 'external_service'
{{project_root}}/lib/external_service/foo.rb
folder hierarchy is convention of rails.
Ruby behaves just like this and it's totally ok.
In this case the Foo-Model is already loaded, so ruby prefers this instead of the local one. Also alphabetically app/ is before lib/
A not so beautiful but quick fix is just to call it like this:
attribute :foo, ExternalService::Foo

Way to load folder as module constant in rails app directory

So have a rails 5 project and would like to load a directory like this
/app
/services
/user
foo.rb
as the constant ::Services::User::Foo
Does anyone have experience in getting rails autoload paths to load the constants in this manner?
foo.rb
module Services
module User
class Foo
end
end
end
SOLUTION
Add this to your application.rb file
config.autoload_paths << Rails.root.join('app')
See discussions here on autoloading
https://github.com/rails/rails/issues/14382#issuecomment-37763348
https://github.com/trailblazer/trailblazer/issues/89#issuecomment-149367035
Auto loading
You need to define Services::User::Foo inside app/services/services/user/foo.rb
If you don't want this weird subfolder duplication, you could also move services to app/models/services or lib/services.
You could also leave foo.rb in app/services/user/foo.rb, but it should define User::Foo.
Eager loading
If you don't need any magic with namespaces and class names, it is pretty straightforward :
Dir[Rails.root.join('app/services/**/*.rb')].each{|rb| require rb}
This will eagerly load any Ruby script inside app/services and any subfolder.

Can I force a namespace for Rails autoloading without nesting in a folder?

Is there a way to tell Rails that all files in a certain folder are contained in a certain namespace?
Ie.
I have a file bar.rb in app/foo. Rails will assume this file defines Bar, but instead I want this file to define Foo::Bar.
I know I can achieve this by adding my root to Rails' autoload paths, but that isn't a real solution. Is there any other way I can tell Rails that all files within app/foo reside in the Foo namespace?
EDIT: File tree for clarification
app
assets
controllers
models
foo
bar.rb
quux.rb
I would like to be able to define Foo::Bar and Foo::Quux in respectively bar.rb and quux.rb, while also using Rails autoloading. Without having to resort to the tree structure as below:
app
assets
controllers
models
foo
foo
bar.rb
quux.rb
You can autoload files with namespaces corresponding to directories inside /app by adding /app to your autoload paths in your application config.
# config/application.rb
config.autoload_paths << "#{config.root}/app"
I'm not sure whether or not this is what the author of the question meant by
I know I can achieve this by adding my root to Rails' autoload paths, but that isn't a real solution.
But this is definitely a real solution and the correct one. Doing this is fine and not at all unusual in my experience.
There are only minor side effects which are the cost of adopting the inconsistent naming conventions you want. If you refer to an undefined constant that matches the name of another directory in the app you'll get a slightly different error message.
# Models::Foo => LoadError (Unable to autoload constant Models::Foo, expected /app/models/foo.rb to define it)
But if you have existing namespaces that match directories in app loading will still work fine. Here's what Rails is doing:
It takes the paths added to config.autoload_paths and adds them to the defaults (the directories under app: app/models, app/controllers, app/foo etc). Then when a constant is referenced that is not already loaded Rails proceeds through those paths, looking for paths that match the constants. So when you reference Foo::Bar it looks for app/models/foo/bar.rb, app/controllers/foo/bar.rb etc. until it finds a file that defined Foo::Bar. All we're doing is adding app/foo/bar.rb to that lookup.
Rails doesn't assume namespace, it assumes path to the source file depending on a namespace, so:
$ cat app/foo/bar.rb
module Foo
class Bar
def bar
puts "i'm here"
end
end
end
$ rails c
2.2.0 :007 > b=Foo::Bar.new
=> #<Foo::Bar:0x00000005272770>
2.2.0 :008 > b.bar
i'm here
=> nil
You can try adding the folder you Rail's autoload paths configuration
# config/application.rb
# Rails 4
config.autoload_paths << Rails.root.join("app", "foo")
# Rails 5+
config.eager_load_paths << Rails.root.join("app", "foo")

Rails namespaced model conflicting with non-namespaced model

I have two classes in rails 3.2.6:
class Foo in models/foo.rb (is in table foos, per default)
class Bar::Foo in models/bar/foo.rb (which sets self.table_name to bar_foos)
When I go into console, I get:
> Bar::Foo
=> Bar::Foo(id: ...)
> Foo # or ::Foo
LoadError: expected models/bar/foo.rb to define Foo
What's wrong?
We solved this in IRC, but the core issue is that there was a config.autoload_paths glob set that was including models/** as load paths.
Rails' auto-loader iterates the load paths, and tacks on the constant name. Once it finds a file that exists, it tries to load it, then throws an exception if the constant is not available.
So, what was happening is Rails had a list of load paths like:
/models/bar/
/models/
It was iterating the paths, and would find a match at /models/bar/foo.rb, which it then loads (which makes Bar::Foo available, but not Foo), then throws the exception because Foo isn't available.
The solution in this case was to remove the autoload_paths setting, so that Rails would not find the wrong file to load for the root-level constant.
Turns out that this line in config/applications.rb was the problem:
config.autoload_paths += Dir[Rails.root.join('app', 'models', '{**}')]
With explicitly set autoload, Rails got confused; rather than look under models/ by namespacing appropriately, it looked at the first autoload file it had (which was, mistakenly, models/bar/foo.rb) and found (true) that it failed to define Foo (it defines Bar::Foo).
So evidently Rails 3 already knows to look in models/ subdirs for namespaced models.
Thanks to Antiarc on freenode #RubyOnRails for helping figure this out.

How to instantiate a class from a different folder structure (Rails)

Using Rails 3.2 and ruby 1.9.3p0
I am trying out the gem delayed_job. I have created a file lib/mailing_job.rb in which I have class MailingJob.
In a controller under app/controllers/requests_controller.rb I am calling
job = MailingJob.new(#request)
but this is returning the error
uninitialized constant RequestsController::MailingJob
I think it is because I need a proper way of referencing a class under a different folder structure.
Any idea how I can isntantiate class MailingJob from a different file (class) in a different folder?
Rails 3 does not include the lib folder within the load path so your application does not know how to find the class.
You can modify config/application.rb and add a line to instruct rails to also look in the lib folder like so
config.autoload_paths += %W(#{config.root}/lib)
In an initializer (e.g. config/initializers/delayed_job.rb), do this (doesnt matter where)
require 'mailing_job'

Resources