Rails , Model created inside a namespace cannot be accessed - ruby-on-rails

I have created a model named "RandomStuff" inside namespace "Module".. but when i try Module::RandomStuff in rails console. it gives the following error
Module::RandomStuff(Table doesn't exist)
and cant access it anywhere in my rails app. when i run ActiveRecord::Base.connection.tables in irb the table "module_random_stuffs" is listed there..
Here are the steps i followed , Let me know if i am missing something
rails generate model Module::RandomStuff
then added one column in the migration file
class CreateModuleRandomStuffs < ActiveRecord::Migration
def change
create_table :module_random_stuffs do |t|
t.string :test_column
t.timestamps null: false
end
end
end
and then ran the following command
rake db:migrate
Still i am unable to access the model any where..

Thanks #DVG for pointing it out.
The following line was missing in my model Modul::RandomStuff
self.table_name = 'module_random_stuffs'

I just love these situations.
Sometimes, when Rails fails to load something it acts as if that something never existed. You can run into this issue if, for some reason, some constant (class or module at least, I've seen with both) had a mistake in its definition, but since it was optional, no error was thrown.
I actually ran into the same issue when using active_model_serializers. It tries to infer a serializer class from model's name. If it fails to find a matching one, it falls back to "serializerless" rendering. That confused me for a second, I was pretty sure I defined it, even in the proper file.
In your specific case Rails has to define a module called Module in order to specify a common table prefix there. Like so:
module Module
def self.table_name_prefix
'module_'
end
end
But try defining a module Module in irb and Ruby will refuse:
module Module
end
# TypeError: Module is not a module
It's not a module? Huh. Then what is it?
Module.class
# => Class
A class? That's right, moreover, it's a Ruby core class. So just choose a different module name. What you've chosen collides with something really essential.
Hacky mode (never actually do this, otherwise... wat)
You can fix that by altering the generated module.rb, replacing module with class. That will reopen the Module class and define your model inside it, making things work as expected. But technically, this is an absolutely unnecessary monkeypatch.

Related

Make Rails autoloading/reloading follow dynamic includes

Context
I want to add some admin specific code to all models via concerns that are automatically included. I'm using the naming convention MyModel => MyModelAdmin and they're in the standard Rails directory app/models/concerns/my_model_admin.rb. Then I can glob over all of them and do MyModel.include(MyModelAdmin).
Issue
Dynamic includes work fine, but when changing the concern in development Rails doesn't reload it properly. In fact the concern seems to get removed.
Reproduction
app/models/my_model.rb
class MyModel
end
app/models/concerns/my_model_admin.rb
module MyModelAdmin
extend ActiveSupport::Concern
def say
"moo"
end
end
config/initializers/.rb
MyModel.include(MyModelAdmin)
So far so good, MyModel.new.say == "moo".
But now change say to "baa" and you get NoMethodError undefined method 'say'.
Notes
I tried a number of things that didn't help:
require_dependency
Model.class_eval "include ModelAdministration"
config.autoload_paths
Using another explicit concern in ApplicationModel with an included hook that includes the specific concern in each model.
ActiveSupport.on_load only triggered on Base not each model.
Does this mean Rails can only autoload using static scope? I guess Rails sees the concern change, knows the model has it included, reloads the model but the static model definition doesn't have the include so the concern goes missing and stops being tracked. Is there a way to force Rails to track dynamically included modules?

How to namespace in ruby

I've got a rails model called LeadSource
class LeadSource < ActiveRecord::Base
end
I also have in the lib directory, the following structure:
-lib
-reports
base.rb
lead_source.rb
in the lead_source.rb, i've got this defined:
module Reports
class LeadSource < Reports::Base
end
end
When I call LeadSource.new in the console, I get the following error:
LoadError: Unable to autoload constant LeadSource, expected ..../lib/reports/lead_source.rb to define it
I am under the assumption that the module Reports should be namespacing my LeadSource class. (allowing me to have two classes named the same thing). But why in this case, when I try to instantiate a new LeadSource object (active record model) is rails looking in lib and throwing a fit about my class defined there?
I would expect to call the active record model like this:
LeadSource.new
and the class in my lib like this:
Reports::LeadSource.new
without them colliding and causing issues.
EDIT:
I feel like this defeats the purpose of namespacing if the solution is to just change the class name as pointed out here as well as the similar question marked as duplicate of this one.
Am I wrong in thinking that a namespace should silo off the resource and not just make it so i have to type more to call it? seems silly to me.

Naming conventions for custom dir under /app/ in a Rails app?

I have question about naming conventions and autoloading.
I want to have a presenter ItemPresenter in app/presenters/items/item_presenter.rb
My understanding was that I can just create that file like this:
module Items
class ItemPresenter
end
end
But when I do this and try to call the presenter as Items::ItemPresenter I get uninitialized constant error:
uninitialized constant Items::ItemPresenter
def show
#presenter = Items::ItemPresenter.new # this is the highlighted line of my Controller
EDIT: Rails, Custom Folders and Namespaces is not duplicate because it's about different dir structure jobs/smth.rb while I am trying to implement presenters/items/item_presenter.rb (1 more level)
EDIT2: neither it works from rails console: NameError: uninitialized constant Items::ItemPresenter
EDIT2: I tried doing this as suggested:
module Presenters
module Items
class ItemPresenter
def test
"hello"
end
end
end
end
And #presenter = Presenters::Items::ItemPresenter.new in my controller:
uninitialized constant TrialsController::Presenters
It seems like Rails do not see that directory at all.
EDIT3: Created a sample app https://github.com/dontlookforme/test_app
EDIT4: Figured it out. I screwed up the file name (see the answer I've posted)
I found the answer but it's necessary to see #user1556912's sample app (link in the original question) to see what happened.
The problem is that the filename is items_presenter.rb (plural) but the class name is ItemPresenter (singular).
As I pointed out in a comment on #Anthony E's answer, Rails will autoload everything in the /app dir, so it's not necessary explicitly to tell Rails about these files. However, along with namespaces matching dir hierarchies, the names of the classes must also match the names of the files exactly. In this case, I was able to get the class to load in the rails console by renaming items_presenter.rb to item_presenter.rb.
Going back to #Anthony E's answer, though, I do agree that the Items:: namespace seems superfluous here. I would just do app/presenters/item_presenter.rb.
app/presenters/ is the conventional path to store presenters. In fact, you can probably go without folder nesting for items:
app/presenters/item_presenter.rb
You'll need to update the module path accordingly:
module Presenters
class ItemPresenter
def test
"hello"
end
end
end
Then, you can tell Rails to automatically load this file in your application.rb:
config.autoload_paths << '#{config.root}/app/presenters'
Ugh. The thing I was doing wrong is the file name.
I named the preseter file items_presenter.rb but the class had singular Item in the name ItemPresenter.
Fixed that and everything started working.
Thanks for the help guys!

Ruby on Rails Monkey Patching a Gem's Model

This might be silly, but I'm including a gem which represents all the models I need for my project. I want to add a method, to_custom_string to one of those models, Person.
I was attempting to do this via (following this example):
config/initializers/extensions/person.rb
Which contained something like:
class Person < ActiveRecord::Base
def to_custom_string
address.street.to_s
end
end
The Person class in the gem has a has_one :address association.
The problem I was experiencing was that this patch seems to override the Person class from the gem, instead of patching it. What's crazy is that this override behavior was only experienced via rake (all of the associations declared in the Person class from the gem are lost).
My rake task was something like:
namespace :convert
task :all_persons => :environment do
Person.where(:param => value).includes(:address).find_in_batches(:batch_size => 2000) do |persons|
persons.each do |person|
puts person.to_custom_string
end
end
end
end
calling bundle exec rake convert:all_persons gave me:
Association named 'address' was not found; perhaps you misspelled it?
But copying and pasting the code in the rake task into rails console worked fine.
My current solution is to copy the code for Person from the gem into my app/models directory, and have my to_custom_string method there, which I know is wrong.
Can someone please explain why a) irb preserved my Person associations, but rake did not, and b) how I can get rake to cooperate?
Thank you!
First of all instead of reopening the class I would create a Module and include it into the Person. So it would look like that
module CustomString
def to_custom_string
address.street.to_s
end
end
Person.send(:include, CustomString)
Also it seems like the Person model is not yet available at the point of running the initializer. You may want to put this in your application.rb if still doesn't work.
config.railties_order = [ModelEngine::Engine, :main_app, :all]
I guess the reason why it works in irb and not in rake is because they look up classes differently. Irb (which I believe you run by running rails console) loads all the classes at once therefore it loads the classes from engine, then it runs the initializer where you have the classes from engine already defined. I guess (though I'm not sure) Rake in development mode uses lazy loading of constants. So it doesn't load all the classes at the very beginning and only when it finds a constants that is undefined. Then it starts looking for a file that may define that constant. Since you put some Person in initializer it doesn't look up the engine's model at all cause at the point it sees Person it has the Person definition already. That's why the inclusion of module instead of reopening the class may help -> it enforces that it will lookup the Person constant from engine.
I think it will work as long as you just reopen the class, without inheriting from ActiveRecord::Base again. So, like this:
class Person
def custom_string
address.to_street.to_s
end
end
Edit:
You might also need to add a line like this before you reopen the class:
require_dependency ModelEngine::Engine.root.join('app', 'models', 'person').to_s
where ModelEngine::Engine is just the class for the engine that contains all your models.

Uninitialized constant trying to reopen a Class

I am using a plugin in Rails, and I call its methods without problems:
plugin_module::class_inside_module.method_a(...)
I want to re-open the class_inside_module and add a new method, I tried in many different ways. I can't figure out why in this way doesn't work:
class plugin_module::class_inside_module
def new_method
puts 'new method'
end
end
I get the error: uninitialized constant plugin_module, but how is possible if I can call without problem plugin_module::class_inside_module.any_methods ?
Do you know why I get that error ? why "uninitialized constant" ? (it is a class declaration :-O )
Do you have any ideas how I can add a new methods in a class inside a module (that is part of a plugin) ?
Thank you,
Alessandro
If you have written your class and module-names like you did, so plugin_module instead of PluginModule this is against ruby/rails standards, and rails will not be able to automatically find the class and module.
If you write something like
module MyModule
class MyClass
end
end
Rails will expect this file to be located in lib\my_module\my_class.
But this can always easily be overwritten by explicitly doing a require.
So in your case, when you write
module plugin_module::class_inside_module
Rails will not know where to find the module plugin_module.
This way of writing only works if module plugin_module is previously defined (and loaded).
So either add the correct require, or rename your modules to standard rails naming, or write it as follows:
module plugin_module
class class_inside_module
This way will also work, because now the order no longer matters.
If the module is not known yet, this will define the module as well.
Either you are re-opening the class, or you define it first (and the actual definition will actually reopen it).
Hope this helps.
Have you tried reopening the module that's wrapping the class, rather than relying on ::?
module plugin_module
class class_inside_module
def new_method
puts 'new_method'
end
end
end
By the way, you know that the proper name for modules and classes is use CamelCase with a capital first letter?
module PluginModule
class ClassInsideModule
def new_method
puts 'new_method'
end
end
end

Resources