Rails mountable engine with isolate_namespace but without prefixed namespace on tables - ruby-on-rails

Is there a way to configure the isolate_namespace method to not use prefixed table names?
class Engine < ::Rails::Engine
isolate_namespace MyEngine
end
Additionally, an isolated engine will set its name according to namespace, so MyEngine::Engine.engine_name will be “my_engine”. It will also set MyEngine.table_name_prefix to “my_engine_”, changing the MyEngine::Article model to use the my_engine_articles table.Isolated Engine Docs
When designing a prototype I ran into an issue where I need the routes to use the isolated namespace pattern, but the database tables do not. This is because the mountable engine I am writing has it's own self contained database.
Don't want to dig much further if it's not possible.

Rails 3 and 4
Did a little digging into the Rails Engine codebase to find a solution.
If you define a method to specify the table name prefix (in /lib/my_engine.rb), it will just use that instead. So set returning nil works fine.
require "my_engine/engine"
module MyEngine
# Don't have prefix method return anything.
# This will keep Rails Engine from generating all table prefixes with the engines name
def self.table_name_prefix
end
end

For Rails 5, the solution seems to be declaring the following on whatever models you want within your engine:
self.table_name = "name_you_want"
This won't affect generation, but accomplishes the use case that the original poster asks about, I think.

Related

When two Rails engines define a method of the same name, which one overrides

If a Rails app has two engines that define the same method, which version "wins"? How is it decided which one gets executed when the method's name is evoked?
For example, both the Rails engine blacklight and another Rails engine meant to modify Blacklight's functionality define a method like this:
module Blacklight
class SuggestSearch
def suggestions
...
end
end
end
The method definition is the same in the two engines but with ... in place of different content. The second of the engines is meant to override the functionality of the first.
The two engines are used in the same app, like this:
gem 'blacklight'
gem 'trln_argon'
How does rails know which engine modifies the other? How is the second engine's definition of suggestions made to prevail?

Requiring Rails Engine Models In Referencing Application

Is there a way to automatically require models from an external Rails Engine in a Rails app without explicitly referencing the Engine's path (in my case an ugly relative path)?
I'm trying to add automatic generation of decorator for a set of subclasses defined in the engine, but BaseClass.descendants only lists descendants that have been already required.
EDIT: Some further details- I have a Rails Engine which defines a set of models:
class BaseModel < ActiveRecord::Base
end
class FirstSubmodel < BaseModel
end
class Second Submodel
end
The engine is referenced in another Rails project's Gemfile, like so:
gem 'my_engine', path: '.../.../plugins/my_engine'
The Rails project needs to automatically generate decorators for each of the Submodels on initialization, like so:
BaseModel.descendants.each {|descendant| generate_decorator(descendant)}
However, 'descendants' returns an empty array since FirstSubmodel and SecondSubmodel haven't yet been loaded.
I ended up using MyEngine::Engine.root, like so:
Dir.glob(MyEngine::Engine.root + "app/models/*_submodel.rb").each { |c| require c }

Accessing helpers from the parent app in an isolated Rails engine

I'm writing a configurable Rails engine. I have an authentication_helper configuration option to define which helper should be called in a before_action in all controllers needing authentication.
The problem is that I don't have access to the parent app's helpers from the engine's controllers. My understanding is that this happens because the engine is isolated.
I have considered using a block instead of a method name, but I'm not sure if that would work, or if I would be able to cleanly access the authorization logic from outside my controllers.
Active Admin, which I have used in the past, has a similar configuration option. I have noticed that their engine is not isolated, so perhaps I'm overrating the importance of engine isolation?
Is there an elegant way to have the benefits of engine isolation while also allowing this kind of customization? Or should I just forego isolation altogether?
EDIT #1
Brad Werth pointed my in the right direction, as this works with a regular controller inheriting from ApplicationController::Base:
module MyBigFancyEngine
class Engine < Rails::Engine
isolate_namespace MyBigFancyEngine
config.to_prepare do
# Make the implementing application's helpers available to the engine.
# This is required for the overriding of engine views and helpers to work correctly.
MyBigFancyEngine::ApplicationController.helper Rails.application.helpers
end
end
end
However, my engine's ApplicationController inherits from RocketPant::Base, which does not provide a helper method. I've tried to use a simple include (which works fine for regular controllers), but that doesn't work either (the controller can't find the helper).
Any ideas?
You can expose the implementing application's helpers available to the engine by including the following code in your engine.rb file:
engine.rb
module MyBigFancyEngine
class Engine < Rails::Engine
isolate_namespace MyBigFancyEngine
config.to_prepare do
# Make the implementing application's helpers available to the engine.
# This is required for the overriding of engine views and helpers to work correctly.
MyBigFancyEngine::ApplicationController.helper Rails.application.helpers
end
end
end
The RailsAdmin Engine is also isolated, but they have the same configuration options as you would like to implement. They have configurable before_filters for both authentication and authorization. Have a look at this.
As far as I can tell, they just subclass the parent controller like this::ApplicationController or instead you can configure one (ref).
For your controller you could just create your own EngineController, that inherits from RocketPant::Base and maybe just create a method there that calls the configured authentication method directly via send on the parent controller.
As the RocketPant::Base Class does not inherit from ApplicationController::Base I guess you have to find some custom way around this and can't go the normal ways for Rails Engines. Maybe you could also try to file an issue to the rocket_pant repo, to add the helper method. As far as I read they soon want to inherit from ApplicationController::Base anyway in 2.0 (ref).
I think you impulse on keeping the isolation is good - because a solution that relies on a method 'just being there' is a pain to debug if s.th. goes wrong in the app using your gem (i.e. typo in method name).
Further if your gem evolves, an some day it won't need the method, in an explicit way it's very convient to give a meainingful error:
You could provide a config method to be called in an initializer:
YourGem.configure do |config|
config.add_callback { MyApp.doIt() }
end
I found this discussion particularly insightful. There are also some interesting ideas in the Rails Engine API under Isolated engine helpers.
The Rails Engine API docs helped me figure out a good solution for url_helpers
You probably moved on by now, but if anyone else needs access to the "parent app" application helpers. You could always just include it explicitly in the application controller in your engine. like so:
include Rails.application.helpers

Extend a Model from a Rails Engine (not replace it)

I have a Rails app that uses a gem called ActsAsTaggableOnSteroids, which is a Rails Engine. Specifically, I'm using PavelNartov's fork of the gem. But nevermind that.
I need to add specific functionality to the Tag model, which is supplied by the engine.
But, according to my understanding of Rails engines and the magical loading functionality in Rails, if I put a file called "tag.rb" in my models directory, then it will completely replace the one from the Engine.
Ideally, I would be able to do something like:
class Tag < ActsAsTaggable::Tag
# my stuff
end
...but alas, that doesn't work because the model supplied by the engine is not namespaced.
So, I came up with this nightmare, which I put in app/models/tag.rb:
path = ActsAsTaggable::Engine.config.eager_load_paths.grep(/models/).first
require File.join(path, 'tag')
Tag.class_eval { include TagConcern }
But there has to be a better way! I feel like I'm missing something. I'd prefer not to add this strangeness to my app if possible.
Just require the file by looking up the path of the gem's model:
require File.join(Gem::Specification.find_by_name("bborn-acts_as_taggable_on_steroids").gem_dir, 'app/models/tag')
Tag.class_eval do
# ...
end

Rails class loading skips namespaced class when another class of same name in root namespace is loaded

I have two namespaces, each with its own controller and presenter classes:
Member::DocumentsController
Member::DocumentPresenter
Guest::DocumentsController
Guest::DocumentPresenter
Both presenters inherit from ::DocumentPresenter.
Controllers access their respective presenters without namespace specified, e.g.:
class Guest::DocumentsController < ActionController::Base
def show
DocumentPresenter.new(find_document)
end
end
This usually calls presenter within same namespace. However sometimes in development environment I see base ::DocumentPresenter is being used.
I suspect the cause is that base ::DocumentPresenter is already loaded, so Rails class auto-loading doesn't bother to look further. Is this likely the case? Can it happen in production environment too?
I can think of two solutions:
rename base class to DocumentPresenterBase
explicitly require appropriate presenter files in controller files
Is there a better solution?
You are correct in your assumptions - If you do not specify namespace, Ruby starts from current namespace and works its way up to find the class, and because the namespaced class is not autoloaded yet, the ::DocumentPresenter is found and autoloader does not trigger.
As a solution I would recommend renaming ::DocumentPresenter to DocumentPresenterBase, because this protects you from bugs when you forget namespacing or explicit requiring somewhere.
The second option to consider would actually be using specific namespaced classnames all over the place, but this suffers from bugs when you accidentally forget to namespace some call.
class Guest::DocumentsController < ActionController::Base
def show
Guest::DocumentPresenter.new(find_document)
end
end
Third option would be your second - explicitly require all the classes in initializer beforehand. I have done this with Rails API which receives embedded models in JSON and Rails tends to namespace them when the actual models are not loaded yet.
Option 3.5 You could probably trick autoloader to do the heavy lifting (though, this might seem more like a hack):
class Guest::DocumentsController < ActionController::Base
# trigger autoload
Guest::DocumentPresenter
def show
# This should refer Guest::DocumentPresenter
DocumentPresenter.new(find_document)
end
def show
# As will this
DocumentPresenter.new(find_document)
end
end
Still the cleanest would be to rename the base class.
I think in 3 solutions if you want to mantein the name, one is your second solution.
1) explicitly require appropriate presenter files in controller files
2) Execute the full environment class path, like:
class Guest::DocumentsController < ActionController::Base
def show
Guest::DocumentPresenter.new(find_document)
end
end
3) Create a file on initialize directory and execute require manually (the worst options :S)

Resources