Inheriting from custom classes within models in rails - ruby-on-rails

Just curious on how things autoloading works in rails
app/models/base_facebook_object.rb
class BaseFacebookObject
end
In the file where I require it,
app/models/fb_campaign.rb
class FbCampaign < BaseFacebookObject
end
But the above doesn't work as it says BaseFacebookObject not defined?
Do I always have to require the base object file in such cases?
require "#{Rails.root}/app/models/base_facebook_object"
Thanks in advance

The autoloader will resolve unfound constant. In your case if you call FbCampaign it will check for the file fb_campaign.rb, then during the loading will find BaseFacebookObject and load base_facebook_object.rb
In other words, no requires needed. Actually you should never use require in a RoR application except for std lib not yet loaded (eg. securerandom, base64 etc. ) or files into a lib/non-autoloaded path.

Related

How can I extend gem class in Rails 6/Zeitwerk without breaking code reloading?

How do I extend a class that is defined by a gem when I'm using rails 6 / zeitwerk?
I've tried doing it in an initializer using require to load up the class first.
I've tried doing it in an initializer and just referencing the class to let autoloading load it up first.
But both of those approaches break auto-reloading in development mode.
I've tried putting it in lib/ or app/, but that doesn't work because then the class never gets loaded from the gem, since my new file is higher up in the load order.
There is a similar question here, but that one specifically asks how to do this in an initializer. I don't care if it's done in an initializer or not, I just want to figure out how to do it some way.
What is the standard way of doing something like this?
I do have one nasty hack that seems to be working, but I don't like it (update: this doesn't work either. reloading is still broken):
the_gem_root = $LOAD_PATH.grep(/the_gem/).grep(/models/).first
require("#{the_gem_root}/the_gem/some_model")
class SomeModel
def my_extension
...
end
end
I know is late, but this was a real pain and someone could find it helpful, in this example I'll be using a modules folder located on app that will contain custom modules and monkey patches for various gems.
# config/application.rb
...
module MyApp
class Application < Rails::Application
config.load_defaults(6.0)
overrides = "#{Rails.root}/app/overrides"
Rails.autoloaders.main.ignore(overrides)
config.to_prepare do
Dir.glob("#{overrides}/**/*_override.rb").each do |override|
load override
end
end
end
end
Apparently this pattern is called the Override pattern, it will prevent the autoload of your overrides by zeitwerk and each file would be loaded manually at the end of the load.
This pattern is also documented in the Ruby on Rails guide: https://edgeguides.rubyonrails.org/engines.html#overriding-models-and-controllers

Rails Module/Folder Naming Convention

I'm having a problem with a module name and the folder structure.
I have a model defined as
module API
module RESTv2
class User
end
end
end
The folder structure looks like
models/api/restv2/user.rb
When trying to access the class, I get an uninitialized constant error. However, if I change the module name to REST and the folder to /rest, I don't get the error.
I assume the problem has to do with the naming of the folder, and I've tried all different combos of /rest_v_2, /rest_v2, /restv_2, etc.
Any suggestions?
Rails uses the 'underscore' method on a module or class name to try and figure out what file to load when it comes across a constant it doesn't know yet. When you run your module through this method, it doesn't seem to give the most intuitive result:
"RESTv2".underscore
# => "res_tv2"
I'm not sure why underscore makes this choice, but I bet renaming your module dir to the above would fix your issue (though I think I'd prefer just renaming it to "RestV2 or RESTV2 so the directory name is sane).
You'll need to configure Rails to autoload in the subdirectories of the app/model directory. Put this in your config/application.rb:
config.autoload_paths += Dir["#{config.root}/app/models/**/"]
Then you should be able to autoload those files.
Also, your likely filename will have to be app/model/api/res_tv2/user.rb, as Rails uses String.underscore to determine the filename. I'd just call it API::V2::User to avoid headaches, unless you have more than one type of API.

Rails include module in model trouble

I have module in /lib/models/scopes.rb
module Models
module Scopes
extend ActiveSupport::Concern
...
end
end
I'm trying to include it from model:
class User < ActiveRecord::Base
include Models::Scopes
end
And getting error:
NameError: uninitialized constant User::Models
How to solve this trouble? Maybe it`s wrong to keep this types of files in /lib?
Environment:
Rails v3.1
Ruby v1.9.3
Rails doesn't require files in the lib directory automatically, but you can add to the autoloaded paths in config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Restart the server to pick up the new settings.
This will now load the file automatically when the module name is first used. In development mode, you might want to reload the module after every change in order to see the changes without restarting the server. To do that, add it as an eager load path instead:
config.eager_load_paths += %W(#{config.root}/lib)
The scope shouldn't be a problem as long as you don't have a Models class or module within User or anywhere else.
when you define your class, you're "opening" a new scope. So when you do Models::Scopes, ruby is looking for User::Models::Scopes. You can fix this by using ::Models::Scopes, the :: telling ruby to look in the global scope.
FYI: I'm not sure about the terms I used or even if my train of thought if correct; but the solution should be good anyway. I'd think Ruby would try for ::Models::Scope after failing to find User::Models::Scope, but it doesn't.. Maybe there is a User::Models scope defined somewhere? Anyway, as you can see, I'm not yet familiar with those. You might want to dig on the subject if that interests you

Require two identically named classes in ruby

In my rails application I connect to another ruby application foo that has a class Database in its folder /classes/database.rb and to another application bar that has another class Databse in its folder /classes/database.rb
In that application I have to put the requires in the application controller. That is done by the following code
$: << "C:\foo\
require "plug_in_foo.rb"
in the plug_in_foo.rb there are the requires of the foo project, i.e.
require "/classes/database.rb"
as in the foo project so for the bar project
$: << "C:\bar\
require "plug_in_bar.rb"
and in the plug_in_bar.rb there are the requires of the bar project, i.e.
require "/classes/database.rb"
The Database class in the bar project has its own module wrapped around it but when I try to access the class by BaRModule::Datbase.new it is recognized as an unitialized constant so I think that the require does not work.
Can someone help me with this? Is there a workaround for this?
You can require the absolute paths to the different database files.
(You might also check, if you really need to add all this stuff to your load path)
you should include both of your module in your class like include BaRModule

Ruby on Rails: Learning ActionController class - Question on $:.unshift activesupport_path and autoload method

Inside ActionController class (rails/actionpack/lib/action_controller.lib) I found several weird code. I don't really have a mentor to learn Ruby on Rails from, so this forum is my only hope:
Question #1: Could anyone help me explain these lines of codes?
begin
require 'active_support'
rescue LoadError
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
if File.directory?(activesupport_path)
$:.unshift activesupport_path
require 'active_support'
end
end
Especially the line with $:.unshift activesupport_path
In my thought, it tries to require active_support class, and if that doesn't work, it looks if activesupport_path is a directory, if it is, then . . . I totally lost it.
Question #2: What autoload method is for?
module ActionController
# TODO: Review explicit to see if they will automatically be handled by
# the initilizer if they are really needed.
def self.load_all!
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
end
autoload :Base, 'action_controller/base'
autoload :Benchmarking, 'action_controller/benchmarking'
autoload :Caching, 'action_controller/caching'
autoload :Cookies, 'action_controller/cookies'
.
.
.
Question #3: If I later find a method I don't understand what for, how is the best way to find out? As for that autoload method case, I tried to find it across my project (I have my Rails code frozen there) but couldn't find any clue. I searched for "def autoload". Am I doing things wrong? Is my IDE, TextMate just doesn't cut it?
Thank you!
In order for a file to be required you have to ensure that the path to it is in the Ruby $LOAD_PATH variable. This is has a short-hand version $: for legacy reasons, inheriting this from Perl.
When you call require, the interpreter looks for a .rb file in each of the paths given there until it finds a match. If it finds one, it is loaded. If not you get an exception.
Often you will see lines like this in files:
# script/something
# This appends "script/../lib" to the $LOAD_PATH, but this expands to
# something like "/home/user/project/lib" depending on the details of
# your installation.
$: << File.expand_path(File.join('..', 'lib'), File.dirname(__FILE__))
You can use standard Array modifiers on $LOAD_PATH like unshift, push, and <<.
The first block of code is attempting to load active_support and only if that fails does it go about modifying the $LOAD_PATH to include the likely location of this file based on the path to the file making the require call. They do this because typically all gems from the Rails bundle are installed in the same base directory.
The reason for using unshift is to put that path at the highest priority, inserted at the front of the list. The << or push method adds to the end, lowest priority.
When you require a file it is loaded in, parsed, and evaluated, an operation which can take a small but measurable amount of time and will consume more memory to hold any class or method definitions inside the file, as well as any data such as string constants that may be declared. Loading in every single element of a library like ActiveRecord using require will require a considerable amount of memory, and this will import every database driver available, not just the ones that are actually used.
Ruby allows you to declare a class and a path to the file where it is defined, but with the advantage of not actually loading it in at that moment. This means that references to that class don't cause script errors in other parts of your application that make use of them.
You will often see declarations like this:
class Foo
# Declare the class Foo::Bar to be defined in foo/bar.rb
autoload(:Bar, 'foo/bar')
end
When using autoload you need to keep in mind that the class name is always defined within the scope of the module or class declaring it. In this example Bar is within Foo, or Foo::Bar using Ruby naming conventions.
When you make use of the Bar class, the foo/bar.rb file will be required. Think of it as creating a stub Bar class that transforms into the real class once it's actually exercised.
This is a great way of keeping a lot of options open, with many different modules ready to use, but without having to load everything into memory up front.
As for the third question, searchable documentation like APIDock will help you try and find more information on methods. The distinction between Ruby and Rails is often blurred, so you may have to check through both to be sure. Rails adds a lot of methods to core Ruby classes, so don't take the listing of methods available to be complete on either side. They work in conjunction.
Sometimes it pays to search for def methodname when trying to find out about where methodname originates, although this covers only conventional declarations. That method may be an alias from a mechanism like method_alias or may have been dynamically created using define_method, you can never really be sure until you dig around. At least 90% of the methods in Rails are declared the conventional way, though, so most of the time a simple search will yield what you want.

Resources