Rails engines : Controller inheritance and routes - ruby-on-rails-4

I have an engine and in my main app, I have controllers inheriting some engine's controllers.
class UsersController < MyEngine::UsersController
end
How to force my engine to use the sub-controllers without having to recreate all my routes in my main app?
---- with decorators (JensD's solution):
I added to my engine.rb file
config.to_prepare do
Dir.glob(Rails.root + "app/decorators/**/my_engine/*_decorator*.rb").each do |c|
require_dependency(c)
end
end
and the decorator
MyEngine::MyController.class_eval do
end
But super is not possible...
A solution would be to create a sub-controller in my engine and use decorator on it but it seems strange...

I prefer to use this gem for decorators instead of require_dependency:
https://github.com/EPI-USE-Labs/activesupport-decorators
With this gem or your require_dependency you can use:
alias_method :super_index, :index
def index
...
super_index
end

Related

Routes in Rails SAML IdP

I'm trying to get this gem to work with Rails 4 application that will serve as a SAML identity provider.
The thing that is confusing me is the routes and the template I assume should be rendered. In the gem controller, there is this:
def new
render template: "saml_idp/idp/new"
end
My routes are just the basic setup from the example, which I assume should match the action in my custom controller that inherits from the gem controller.
I have this in my controller.
class SamlIdpController < SamlIdp::IdpController
def idp_authenticate(email, password)
true
end
def idp_make_saml_response(user)
encode_SAMLResponse("you#example.com")
end
end
And my routes.rb file:
get '/saml/auth' => 'saml_idp#new'
get '/saml/metadata' => 'saml_idp#show'
So, what am I missing here? There should be a view rendered, instead I'm getting No Route Matches errors. Thanks.
As per Doc, I think you missed including SamlIdp::IdpController module
please include SamlIdp::IdpController rather than excluding.
Hope, It will work.
The new update for saml_idp gem wants to include SamlIdp::Controller as a module. And the controller class can inherit from ApplicationController
In your case it will be:
class SamlIdpController < ApplicationController
include SamlIdp::Controller
end

How to structure namespaced modules

I am having trouble with Ruby class (constant) lookup within the context of a Rails engine gem.
I have a gem MyGem that is a Rails engine. It defines non-namespaced models that are expected to be overridden by the MainApp that would include the gem and namespaced modules, which are included in gem's and main_app's models to have a DRY way of defining reusable code.
Here is a sample code structure:
Two models
# in /app/models/user.rb
class User < ActiveRecord::Base
include MyGem::User::CommonExt
end
# in /app/models/comment.rb
class Comment < ActiveRecord::Base
include MyGem::Comment::CommonExt
end
Their two modules
# in /app/models/concerns/my_gem/user/common_ext.rb
module MyGem::User::CommonExt
def load_comment(id)
return Comment.find(id)
end
end
# in /app/models/concerns/my_gem/comment/common_ext.rb
module MyGem::Comment::CommonExt
def load_user(id)
return User.find(id)
end
end
Now, if I call
User.new.load_comment(1)
I get undefined method #find for MyGem::Comment::Module
I think I understand why this is happening - in the context of #load_comment definition, which is namespaced under MyGem, Comment constant lookup returns MyGem::Comment, rather than the more distant ::Comment
I would prefer not to have to prepend every model instance with ::.
Is there a file structure, model/class definition or configuration change I could use to make a call to Comment return the model Comment, not the MyGem::Comment module?
I would use inheritance instead of mixin in this case.
So in your gem/engine you could define your common class similar to this:
module MyGem
module Common
class Base < ActiveRecord::Base
# common functionality goes here
def load(record_type, id)
record_type.find(id)
end
end
end
end
Then in your main_app code:
class User < MyGem::Common::Base
...
end
Now you could do this:
User.new.load(Comment, 1)
This violates the Law of Demeter but hopefully it illustrates the point.
Doing it like this is DRY and has the added benefit that it prevents your gem from having to know about classes which are outside it's own scope.

correct way to use routes.rb namespace rails?

I'm trying to create a back-end area to my application, so I've created a folder named backend and backend_controller.rb inside it. I need the folder because the backend area will have multiple folders, so it's better-separated from my others.
my routes.rb looks like:
namespace :backend do
get 'index'
end
my backend_controller.rb:
class BackendController < ApplicationController
def index
end
end
But in this mode Rails will search for my backend_controller.rb inside the controllers folder, not in controller>backend. I've tried many variations, and I get routing errors.
So what is correct way to do that? To set the path /backend to index action instead of /backend/index?
Thanks
What i've done:
based on all answers, principally the one from Cyril DD
I've created the backend_controller.rb on the app/controller folder and in the sub-folder app/controller/backend i created the static_pages_controller.rb and all files looks like this:
app/controllers/backend_controller.rb:
class BackendController < ApplicationController
end
app/controller/backend/static_pages_contter.rb:
class Backend::StaticPagesController < BackendController
def dashboard
end
end
routes.rb:
namespace :backend do
resource :static_pages, path: '', only: [] do
root to:'static_pages#dashboard'
end
this works fine, but cause i'm newbie on rails i must ask. This is a good or a conventional way to do that? to administrate the permissions which user can see on the backend i use the backend_controller.rb right? and at last wy i must use resource: instead just get ''
Answering your question
Alright, namespace :something is a shorthand for scope 'something', module: 'something', as: 'something'
Now your declaration is very ambiguous, because you don't specify a controller. Typical declarations look like (assume you have a controller controllers/backend/some_resources_controller.rb and you want to generate default routes)
namespace :backend do
resources :some_resources
end
Now what you did
namespace :backend
get 'index'
end
is really ambiguous and I'm not surprised it doesn't do what you want. Basically you just tell rails to "look inside subfolder 'backend' and define the route 'index'". oO ? Which file/controller are we even talking about ?
What is your backend_controller.rb supposed to do ? Is it some kind of Control Panel ? Dashboard ? If so you're probably gonna have a lot of non-CRUD actions, but anyways you should go for the following syntax
namespace :backend
# Below line of code will auto-generate the `index` for /backend/backend_controller
resource :backend, only: [:index], path: '' do # we need " path: '' " otherwise we'll have https://xxx/backend/backend/dashboard
# If you have non-CRUD actions, put them here !
get 'dashboard' # https://xxx/backend/dashboard
...
end
# However, this will create URLs like "https://xxx/backend/dashboard", etc.
# If you want to redirect https://xxx/backend/ to your backend_controller#index, use root
root to: 'backend#index' # https://xxx/backend/
end
Last thing as mentionned by other guys, when you namespace a file like your Backend_controller inside /backend/ subfolder, you must rename the class like (/controllers/backend/backend_controller)
class Backend::BackendController < ApplicationController
Remark : if you only have like one or two controller actions, instead of using the resource method, you can declare singular resources
namespace :backend do
root to: 'backend#dashboard'
get 'dashboard', to: 'backend#dashboard' # singular resource
end
An Example of what you may actually really want to do...
I'm not sure you are clear yourself about what you want to do. As an example, here is my architecture
Files
/controllers/application_controller.rb
/controllers/backend_controller.rb
/controllers/backend/static_pages_controller.rb
/controllers/backend/***.rb
The class /controllers/backend_controller.rb will not serve any action, but will override ApplicationController to tune it for backend access (but maybe you don't need to do so)
class BackendController < ApplicationController
# Do you need to change user_access method ? Or any other backend-wide config ?
# If so put this config here, otherwise leave empty
end
Now for every file that goes in the /backend/ subfolder, I inherit the backend_controller
class Backend::StaticPagesController < BackendController
def index
end
# Note : if your index is some kind of dashboard, instead I would declare
def dashboard
end
end
class Backend::SomeResourcesController < BackendController
...
end
Routes
namespace :backend do
root to 'static_pages#index' # https://xxxx/backend/
resource :static_pages, only: [:index], path: '' # https://xxxx/backend/index
resources :some_resources
end
If you choose the dashboard solution in your controller, write instead :
namespace :backend do
root to: static_pages#dashboard # https://xxxx/backend/
resource :static_pages, path: '', only: [] do
get 'dashboard' # https://xxxx/backend/dashboard
end
resources :some_resources
end
Then it's simply
# routes.rb
Rails.application.routes.draw do
namespace :backend, shallow: true do
resource :backend, path:''
end
end
Then in your app/controllers/backend/backend_controller.rb, it'd look like this.
class Backend::BackendController < ApplicationController
def index
end
end
When I use rake routes it shows
Prefix Verb URI Pattern Controller#Action
backend_backends GET /backend(.:format) backend/backends#index
Hope this helps.

Serve a file from public in a controller action

In my project, we have a single-page application, and our build process compiles the application into a single, static file at public/index.html.
The single-page application is responsible for handling all the routing in the app, so no matter whether you visit site.com or site.com/foo/bar/action or site.com/☃, I want the Rails application to serve public/index.html
I have this route:
match '*path', to: 'foo#index', via: :all
And in FooController:
class FooController < ApplicationController
def index
render 'index.html', layout: false
end
end
This does not work as I would hope; I get the error Missing template foo/index.html, application/index.html
Is it possible to serve an assets from public as part of a Rails controller action?
In Rails 5 I got this to work by doing
render file: "public/examplename.html", layout: false
This was way easier than I thought it would be.
I changed:
render 'index.html', layout: false
to
render 'public/index.html', layout: false
and everything worked as expected.
This appears to have changed. I'm trying to temporarily render a static file for the admin portion of my app, which will be a VueJS SPA that talks to rails via an API. I had to do the following:
routes.rb
match '*path', :to => 'console#index', :constraints => { :subdomain=>'console'}, :via=>:all
console_controller.rb
class ConsoleController < ApplicationController
def index
render :file=>'/public/console/index.html', :layout=>false
end
end
So using :file instead of no parameter on the path - because otherwise Rails 5 was trying to treat it like a normal view template.
With that path matching and a subdomain, this seems to work well, as all routes will pass through to the VueJS router.

rails_admin authentication with user's boolean admin

I am currently using rails 4.2.0beta4 with devise. In my schema, my user had boolean :admin default:false. After installing rails_admin, for the time being, every user is allowed to go to my /admin. But how do I make it so that only the users with boolean is_admin? true people log in?
Should I just do rails generate devise admin? Would that be quicker choice than having to configure again?
Thanks in advance.
I'd create an admin namespace...
1 - In routes.rb
namespace :admin do
resources :your_routes
end
2 - Make sure the following directories exist; app/views/admin, app/controllers/admin, and put any controllers and view templates that you want to restrict to admins in those folders.
3 - Add this to application_controller.rb
def user_is_admin?
signed_in? && current_user.is_admin?
end
4 - Then in any controller inside your app/controllers/admin add a before_filter to check
some_controller.rb
class Admin::YourController < ApplicationController
before_filter :user_is_admin?
end
For what it's worth, you don't have to namespace if you don't want to. You could technically just add the before_filter from steps 3 and 4 to whatever action you want to protect, but I think the namespace is a better idea in the long run.