I'm building a Rails engine called Engrave.
I have the engine mounted like so:
# Routes.rb of the host app
mount Engrave::Engine => "/engrave", :as => "engrave_engine"
Within this engine I have a controller called "PostsController". When I navigate to this controller to view a post like so: /engrave/posts/1 I get this error:
undefined local variable or method `new_user_session_path'
The PostsController in the engine is inheriting from the engine controller, which is inheriting from the application controller, like so:
module Engrave
class PostsController < ApplicationController
...
end
class Engrave::ApplicationController < ApplicationController
end
The new_user_session_path is being defined by devise, which I have setup like:
devise_for :users
The call to new_user_session_path is in the layouts/application.html.erb template file in the host app
I cannot figure out why this route helper isn't available in this context. What am I doing wrong?
Use
main_app.new_user_session_path
that should work
I've had success doing the following in the main app's application_helper.rb:
module ApplicationHelper
# Can search for named routes directly in the main app, omitting
# the "main_app." prefix
def method_missing method, *args, &block
if main_app_url_helper?(method)
main_app.send(method, *args)
else
super
end
end
def respond_to?(method)
main_app_url_helper?(method) or super
end
private
def main_app_url_helper?(method)
(method.to_s.end_with?('_path') or method.to_s.end_with?('_url')) and
main_app.respond_to?(method)
end
end
I've used this in mountable engines, so you do not have to sacrifice those features.
Working off of this response, I include all helpers found in application_helpers.rb by stating helper "manager/application" inside the controller (if 'manager' is the current namespace of your mountable engine. Just use 'application' if you are calling this from a standard application).
Related
I have a subfolder api in my controllers folder.
So when I call POST "/api/auth" I would like the program to get there using rails conventions.
I.E. I don't want to write the route for each call, but to use rails "patent" that makes rails go to CRUD actions understanding the PUT, POST, GET by itself.
So in my routes.rb I have:
namespace :api do
resources :debts, :auth
end
But when I POST (or GET) with localhost:3000/api/auth I get:
ActionController::RoutingError (uninitialized constant Api::AuthController)
What am I missing?
Please note that I also need to have many controllers inside the subfolder. Is there a short match for all?
You have to put your controller in a sub folder too en then do e.g.
# /app/controllers/api/debts_controller.rb
module Api
class DebtsController < ApiController
end
end
# /app/controllers/api/auth_controller.rb
module Api
class AuthController < ApiController
end
end
Then in base controller folder:
# /app/controllers/api_controller.rb
class ApiController < ApplicationController
end
You need to namespace the class also in order to get this working.
The following 2 ways can be used:
module Api
class AuthController < ApplicationController
# Your controller code here
end
end
class Api::AuthController < ApplicationController
# Your controller code here
end
If you have some code that needs to be run for every controller inside the Api namespace, you can make an Api base controller, but it's not necessary.
I am using apipie for API documentation.
The problem I am facing now is that the large amount of information to be provided for documentation is ruining my code structure in the controller.
How can I write the documentation outside the controller file,
so that the code structure will be readable?
You can find nice explanation on how to use apipie outside controller in this document https://ilyabylich.svbtle.com/apipie-amazing-tool-for-documenting-your-rails-api
To sum things up (example from link):
# app/docs/users_doc.rb
module UsersDoc
# we need the DSL, right?
extend Apipie::DSL::Concern
api :GET, '/users', 'List users'
def show
# Nothing here, it's just a stub
end
end
# app/controller/users_controller.rb
class UsersController < ApplicationController
include UsersDoc
def show
# Application code goes here
# and it overrides blank method
# from the module
end
end
super also needs to be included in the stubbed actions in the docs. In my app I created a controller that inherits from an existing controller defined in a gem, and if I don't include super in the stubbed action, I get unexpected behavior.
So:
# app/docs/users_doc.rb
module UsersDoc
# we need the DSL, right?
extend Apipie::DSL::Concern
api :GET, '/users', 'List users'
def show
super
end
end
# app/controller/users_controller.rb
class UsersController < ApplicationController
include UsersDoc
def show
# Application code goes here
end
end
I have a module as a mountable engine mounted to a main app
through
mount MyEngine::Engine, :at => '/myengine'
I have everything namespaced in the negine and the engine has it's own views in engine/app/views/myengine/
Everything works fine when I run rails server then try to access
localhost:3000/myengine
first then go to the root of the main app and come back to the engine through a link in the index view of the main app
However when I start the server, go to localhost:3000 and from there click on the link to the engine module it tries to fetch the views correctly however the methods contained in the engine's helpers raises an error upon call for them being undefined.
I am on rails 4
I used eager_load to load the mountable engine in it's initialized in the main app and everything seems to be working now.
Myengine::Engine.eager_load!
I just ran into this issue as well. I had defined an ApplicationController inside my engine and was seeing NoMethodErrors when I tried to use helper methods inside one of my engine's controllers.
The Problem
The code looked something like this:
in my_engine/app/controllers/my_engine/application_controller.rb
module MyEngine
class ApplicationController < ActionController::Base
helper ApplicationHelper
end
end
in my_engine/app/controllers/my_engine/projects_controller.rb
module MyEngine
class ProjectsController < ApplicationController
def new
# some action code here
end
end
end
in my_engine/app/helpers/my_engine/application_helper.rb
module MyEngine
module ApplicationHelper
def translations_include_tag
javascript_include_tag "translations-#{I18n.locale}.js"
end
end
end
If I navigated to a route in the host rails app, then clicked a link that navigated to my_engine/projects/new, I would get a NoMethodError saying translations_include_tag didn't exist.
The Explanation
I think this was happening because the application had two classes with the same name: ApplicationController in MyEngine and ApplicationController in the host app. When the first route outside the engine is visited, Rails autoloads the ApplicationController from the host app. What that means is Rails associates the main app's ApplicationController with the name "ApplicationController". When you navigate to my_engine/projects/new, Rails lazy loads ProjectsController which also inherits from ApplicationController. Rails/ruby thinks you're referring to the same ApplicationController it has already loaded, so in effect ProjectsController ends up inheriting from the host app's ApplicationController instead of the one in the engine. Since the translations_include_tag method isn't defined in the host app's ApplicationController, ruby raises a NoMethodError when the view tries to call it.
The Solution
I was able to fix the problem by explicitly inheriting from MyEngine's ApplicationController:
in my_engine/app/controllers/my_engine/projects_controller.rb
module MyEngine
# changed from just plain 'ol ApplicationController
class ProjectsController < ::MyEngine::ApplicationController
def new
# some action code here
end
end
end
After fixing the inheritance issue, the NoMethodError went away.
The accepted answer says to use eager loading, which will also fix the problem because it forces the engine's ApplicationController to load first. Since the engine's ApplicationController is namespaced inside MyEngine, it's full name is MyEngine::ApplicationController, which doesn't conflict with the main app's ApplicationController because the constant names are different.
You need to include the helpers explicitely in your lib/engine.rb file:
initializer 'your-gem-name.setup_helpers' do |app|
app.config.to_prepare do
ActionController::Base.send :include, HelperModuleName
end
end
end
Mountable Engine Controller Method:
module ServiceApi
module Api
module V1
class RequestorController < ApplicationController
def get_details(query_parameters)
#some code here
end
end
end
end
end
Rails app Controller Calling the get_details(params) method from the Mountable Engine
ServiceApi::Api::V1::RequestorController.new.get_details(params)
It seems to me like you're not looking for a controller action, but rather a helper method. For instance, controller actions don't take parameters.
If you still want it to live in the controller as stated, that's fine, but you can extract it to a helper and include that helper in every controller you want to use it in.
For instance,
# [engine]/app/helpers/requestor_helper.rb
module ServiceApi::Api::V1::RequestorHelper
def get_details(query_parameters)
# code
end
end
and then in your controller
include ServiceApi::Api::V1::RequestorHelper
No need to abuse classes when what you want is a module.
I'm using the bitly gem and would like to have access to the bitly API inside my helper methods (which get called by views and mailers to generate URLs).
I initiate an API connection in this method in my ApplicationController:
(is there a more appropriate place to do this BTW?)
class ApplicationController < ActionController::Base
before_filter :bitly_connect
def bitly_connect
Bitly.use_api_version_3
#bitly ||= Bitly.new(APP_CONFIG['bitly_username'], APP_CONFIG['bitly_api_key'] )
end
end
By default I don't have access to #bitly in my helpers. Can you suggest a way to accomplish that?
The only related thread I found wasn't helpful:
Rails 3 and Controller Instance Variables Inside a Helper
Thanks.
Rails by convention passes instance variables set in the controller actions (and filters) along to the views. The helper methods are available in these views, and should have access to the instance variables you set inside your controller action.
Alternately, you can set a local variable inside your helper method by passing the variable to the method, or by using the Object#instance_variable_get method: http://ruby-doc.org/core/classes/Object.html#M001028
# app/controllers/example_controller.rb
class ExampleController
def index
#instance_variable = 'foo'
end
end
# app/helpers/example_helper.rb
module ExampleHelper
def foo
# instance variables set in the controller actions can be accessed here
#instance_variable # => 'foo'
# alternately using instance_variable_get
variable = instance_variable_get(:#instance_variable)
variable # => 'foo'
end
end
As for your concerns with the placement of the logic, it does not look like it belongs in the controller. Think of the controller as routing requests for your application. Most logic should be performed inside of your model classes. "Skinny controller, fat model.": http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
If you need a controller method to be accessible as a helper, you can use helper_method
class ApplicationController < ActionController::Base
helper_method :bitly_connect
def bitly_connect
#bitly ||= begin
Bitly.use_api_version_3
Bitly.new(APP_CONFIG['bitly_username'], APP_CONFIG['bitly_api_key'] )
end
end
end
Note that I also altered the method, so that it doesn't call Bitly.use_api_version_3 each time it is called.
As Ben Simpson noted, you should probably move this into a Model though.