Accessing models from outside rails engine - ruby-on-rails

Hey I am creating a rails 3 engine and trying to access a model in the application that is mounting the engine.
module MyEngine
class UsersController < ApplicationController
def index
#users = User.all
end
If I call the following then it gives me the error:
Could not find table 'my_engine_users' - Its automatically looking for the namespaced version that would exist if the model was inside the engine, but in this case its defined in the app that uses the engine.
If I call ::User.all instead of User.all then everything works, it looks a bit strange though. Is this valid ruby or is there a better way to get ahold of the Object?

So if I get your question right, you have a model in your engine looking something like this:
module MyEngine
class User
end
end
If the assumption above is right, you can just set the table name via table_name= in the model
module MyEngine
class User
self.table_name = 'users'
end
end

Related

Controllers not being loaded when mounting a Rails Engine

I am currently creating a Rails engine. So far the engine has a few new routes and a few new controllers. The routes work fine, as I was able to mount them adding the following to config/routes.rb:
mount MyEngine::Engine => '/'
The problem comes with the controllers. When trying to access the actions I have defined, I get:
uninitialized constant MyEngine::ApplicationController
I am a bit surprised, as the documentation suggests that everything inside app/ is autoloaded from the engine. I have even tried to explicitly load the controllers in engine.rb, to no avail:
module MyEngine
class Engine < ::Rails::Engine
isolate_namespace MyEngine
Dir["#{config.root}/app/controllers/**/"].each do |path|
config.eager_load_paths << path
end
end
end
I'm confused. Aren't the contents of app/controllers/ supposed to be autoloaded by the application from the engine?
It looks like your engine's controller is trying to reference an ApplicationController inside your engine, when you might have wanted to inherit from the main application's ApplicationController (correct me if I'm wrong).
I don't know how the controller code in your engine looks like (feel free the share if I am not on the right track), but I assume it looks something like this
module MyEngine
class MyController < ApplicationController
end
end
In this scenario, it requires you to also have defined MyEngine::ApplicationController somewhere, and based on the error message, I assume this controller does not exist.
To solve this, you could either define the engine specific ApplicationController, or you can specify that you want to inherit from the root controller, like this:
module MyEngine
class MyController < ::ApplicationController
end
end
If you later want to be even more flexible in your engine, it is often left to an initializer configuration option to set which controller to inherit from (see example from Devise).
module MyEngine
class MyController < MyEngine.parent_controller.constantize
end
end

How does a controller class access a model class?

How does this random controller class
class RandomController < ApplicationController
def index
#user = User.all
end
end
access the User class? I've been searching for the connection in the source files, but I can't seem to find a logical explanation.
Rails has 'constant autoloading', so you don't need to add require 'user' to the top of your file.
http://guides.rubyonrails.org/autoloading_and_reloading_constants.html
When Rails encounters a missing constant, it tries to load a file with a filename based on the constant's name. This nearly always works smoothly... C-;

Accessing a Rails Engine model from within a namespaced controller

I have Rails Engine model that looks something like this:
module Adhocracy
class Membership < ActiveRecord::Base
. . .
end
end
So I would expect to be able to access it with Adhocracy::Membership. However, I'm getting an error in this namespaced controller:
module Api
module V1
class Adhocracy::MembershipsController < ApplicationController
def index
#memberships = Adhocracy::Membership.where(params)
end
end
end
end
The error is:
uninitialized constant Api::V1::Adhocracy::Membership
If I go into this controller with debugger and type in Adhocracy, it returns Api::V1::Adhocracy, while Adhocracy::Membership returns the above error. However, if I go into another controller with debugger (such as Api::V1::SessionsController), Adhocracy::Membership returns the expected model. Any idea what's going on?
Its due to how Ruby works: it first searches in your current classes, then in its ancestors.
So Adhocracy matches Api::V1::Adhocracy in your MembershipsController and it searches Membership there.
Whereas in another controller with no match, the search goes down the ancestor tree until it reaches Object where Adhocracy is defined.
To be sure to get top level constants append :: which leads you to: ::Adhocracy::Membership

How does including a module work inside a controller?

If I do:
rails generate scaffold account/user username
I get a controller that looks like this:
class Account::UsersController < ApplicationController
def index
#account_users = Account::User.all
end
...
end
If I include the Account Module, then it looks like all the database calls don't need to be prefixed with "Account::". I.e.
class Account::UsersController < ApplicationController
include Account
def index
#account_users = User.all #this works because I included the Account Module above
end
...
end
Now if I were to move my
controllers/account/users_controller.rb
file to:
controllers/admin/account/users_controller.rb
The file looks like this (note: I also corrected my routes file after this move):
class Admin::Account::UsersController < ApplicationController
include Account
def index
#account_users = User.all #this call does not work now
end
...
end
But I get an error saying "uninitialized constant Admin::Account::UsersController::User"
It looks like rails is trying to make a database call on the "User" model without the "Account::" module in front of it.
So how does including modules in controllers work? Why does this not work when I move my controller into a different file (and leave the model in the same location from the generated scaffold) but it works with the scaffold generated files? How can I fix this issue?
Resolving the name of a module is done relative to the current module. Try and change it to:
include ::Account
or
include ::Admin::Account
(depending on the module in which your User model is defined)
This will tell ruby to look in the global namespace for the module Account
I guess I didn't realize you can just explicitly require the path to the module you would like to include. I learned this after reading up on modules some more...
So adding an explicit call to "require 'account/user'" just outside the controller class makes it so including the module in the controller works.

Can't access model from inside ActionController method added by a Rails engine

I am developing a Rails engine to be packaged as a gem. In my engine's main module file, I have:
module Auditor
require 'engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
require 'application_controller'
end
module ActionController
module Auditor
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def is_audited
include ActionController::Auditor::InstanceMethods
before_filter :audit_request
end
end
module InstanceMethods
def audit_request
a = AuditorLog.new
a.save!
end
end
end
end
ActionController::Base.send(:include, ActionController::Auditor)
where AuditorLog is a model also provided by the engine. (My intent is to have "is_audited" added to the controllers in an application using this engine which will cause audit logging of the details of the request.)
The problem I have is that when this code gets called from an application where the engine is being used, the AuditorLog model isn't accessible. It looks like Ruby thinks it should be a class in ActionController:
NameError (uninitialized constant
ActionController::Auditor::InstanceMethods::AuditorLog)
rather than a model from my engine.
Can anyone point me in the right direction? This is my first time creating an engine and attempting to package it as a gem; I've searched for examples of this and haven't had much luck. My approach to adding this capability to the ActionController class was based on what mobile_fu does, so please let me know if I'm going about this all wrong.
Use ::AuditorLog to access the ActiveRecord class (unless you have it in a module or namespace, in which case you'll need to include the module name).

Resources