How to use Devise/CanCan to protect mounted Engine resources? - ruby-on-rails

I have an engine mounted to my main app and I want to protect certain controllers and actions within that engine.
The engine is mounted with:
mount SomeEngine::Engine => '/some_engine'
Devise/CanCan is working with the rest of the main app's controllers and actions, but letting things run without anything else produces this error:
This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check.
So I open up the engine controllers from the main app using the decorator approach and add:
load_and_authorize_resource
Then I get this error:
No route matches {:action=>"new", :controller=>"devise/sessions"}
I can get things working using the following, but it's clunky when I try to implement roles:
authenticate :administrator do
mount SomeEngine::Engine => '/some_engine'
end
By clunky I mean I'll have to reproduce the above block of code in the routes.rb file for each role that has access to the engine...unless there's another way to use authenticate with roles that I don't know about???
I'd like to use the normal Devise/CanCan authorization/authentication approach in the controller if possible. But I think "no route match" error occurs because the engine does not know how to get to the main app's Devise controllers. But how do I get around this from the main app?
To throw one more issue into the mix...there is one specific controller/action in the engine that I do want to make public to all users. Thus far I've just added this before the authenticate block of code in the routes.rb file.
match '/some_engine' => 'some_engine/some_controller#public_action'
It works...but this line with the block in the routes.rb seems like I'm doing something wrong. And it doesn't allow me to implement roles nicely.

You can inherit application controller for use devise and cancan from main app.
module SomeEngine
class ApplicationController < ::ApplicationController
before_filter :merge_abilities
private
def merge_abilities
current_ability.merge(SomeEngine::Ability.new(current_user))
end
end
end
After this you can create abilities for engine by create own.
module SomeEngine
class Ability
include ::CanCan::Ability
def initialize(user)
return if user.nil?
can :manage, SomeModel
end
end
end
SomeModel (SomeEngine::SomeModel) is model at SomeEngine engine.
At resource controllers you must specify class name of resource.
load_and_authorize_resource class: SomeEngine::SomeModel
And do not forgot change route helper to main_app.MAIN_APP_PATHS at main application layout if you want to use it at engine.

Related

Variable root in Rails router

I have completely re-written our Single Page Application (SPA) using a different technology, however instead of enforcing new UI to all the users, I would like them to opt to try new UI and similarly switch back to old UI, before enforcing the new UI to every user. The new UI was written keeping this is mind, however in routes.rb I need to define root manually to pick one of them. Ex:
root :to => 'dash#new' # for new UI
or
root :to => 'dash#old' # for old UI
How can this be achieved automatically? Something like:
default root will be 'dash#old'
when user opts to try new UI it should be stored in a new field in User model. (say user.newui = true)
as per the value of user.newui root should be picked. something like:
user.newui ? 'dash#new' : 'dash#old'
However obviously this is not possible. I do not have any user object in routes, most probably my whole solution is pointing south. Can someone please guide me on how to achieve this or whats the best practice?
You'd be best changing the layout in the controller, not middleware...
#config/routes.rb
root "dash#index"
#app/controllers/dash_controller.rb
class DashController < ActionController::Base
before_action :authenticate_user!, :set_layout
def index
current_user.newui?
# define data
else
# old data
end
end
private
def set_layout
layout (current_user.newui? "new" : "old")
end
end
You must remember that your "routes" are like a menu - you pick what you want and browse to it.
What is delivered from those routes is entirely dependent on the controller. In both instances, you're trying to invoke the dash#index action; what you're trying to do is ascertain whether the user has a different preference for how that will be displayed.
Since the middleware is meant to deal with the request (it doesn't know about the User), you'll be best using the controller to make the change.
If you had user object in routes it would be really easy.
Something like devise gem gives that to you out of the box. It allows you also define boolean triggers to user model.
For example I have default false admin tag that actually changes the route. Devise MODEL always gives you routes automatically so if you generate
rails g devise User you will have devise_for :users that you can use in your routes.
devise_for :admin do
root to: "admin#index"
end
root to: "home#index"
Without having user model there you can still define roots in controller but how you you persist them per user?

How to integrate frontend and admin theme together in ruby on rails

I have an application with admin in ruby on rails. Now I need to add front-end in that application. But I don't know how ingrate with both in a single application.
You can create an "admin" area pretty simply once you know how. It all comes down to namespaces, specifically:
#config/routes.rb
namespace :admin do
# Sets up "/admin"
root "application#index"
end
Namespaces are essentially "folders", which also influence the names of your Rails classes (for example, your controller class names).
This means you'll be able to use the following:
#app/controllers/admin/application_controller.rb
class Admin::ApplicationController < ActionController::Base
layout :admin
def index
#do stuff here
end
end
Your models will remain as they are now (no need to make them admin namespaced).
--
The above code should give you the ability to access yoururl.com/admin and have a controller/action to work with. Of course, this negates the fact you're going to have to populate this area with data & controller actions; it all works much similarly to a "standard" rails app once you get it working.
You'll want to check out these helpful resources:
ActiveAdmin gem
RailsAdmin gem
RubyToolbox Admin gems
Railscasts Admin tutorial:

How to run class method via URL in Rails 4

I'm real beginner in Rails.
I created app/services/xclass.rb class with some_method inside.
I need to execute some_method using url.
For example, I want run this method when I execute in my browser url - http://application.com/notifications/send
I think it could be done through controller (notifications_controller) but how to do it?
I created only controller, with no model, just for launching some_method.
first, create a route:
get "notifications/send" => "notifications#some_action", :as => "send_notification"
Then create a controller action in your controller (ie. NotificationsController):
def some_action
Xclass.some_method # run the method you want
redirect_to root_path # redirect or whatever you want here
end
Now you can either visit the path http://your_app.com/notifications/send, or link to is using 'send_notifications_path' url helper in rails.
That should do it
Since you're a beginner, let me give you some ideas
MVC
Firstly, you need to appreciate that Rails is an MVC (model view controller) framework:
In short, this means that every time you send a "request" to Rails, it will be "routed" to the specific controller action which corresponds with that route.
This means that when you ask about how to fire a "class method", you're going to have to work within the confines of the MVC programming pattern. Here's how:
#config/routes.rb
resources :notifications do
get :send, on: :collection #=> domain.com/notifications/send
end
#app/controllers/notifications_controller.rb
class NotificationsController < ApplicationController
def send
#call your class method here
YourModel.class_method
end
end
#app/lib/your_model.rb
class YourModel
def self.class_method
#do something here
end
end
--
Rails
This is further supported by the fact that Rails is just a framework - in fact it's a gem (a great one) which runs on top of Ruby.
This means that even though some of the ways in which Rails works might seem somewhat alien to begin with, you have to remember that it basically just captures "requests" fed to it by a web sever, processing them with connectivity to the database etc.
The issue here is that as you're sending the request over HTTP, you have to work within the constraints of this protocol (specifically that it's stateless), and with Rails. As mentioned, Rails is MVC-based, which means that every request will be routed to your controller, which is why you have to create the corresponding route & controller action to handle it
If you use the code above (tweaked to your app), it should work for you

Alias route's name

I need to have one path accessible through multiple names. In my routes.rb I did
get '/route' => 'controller#edit', :as => 'name_a'
get '/route' => 'controller#edit', :as => 'name_b'
This works nicely but loads the routes table for nothing. From my understanding of the documentation, :as defines a helper method when called.
So I went to my ApplicationController and added
alias_method :name_b, :name_a
and I removed the second line from routes.rb
but that fails with Uncaught exception: undefined method name_a for class ApplicationController
is there any proper way of having two names for a single path?
=================EDIT====================
Elaboration:
I use Devise gem to manage session, registration, locking, etc. of 2 kinds of users, let's call them Admin and Guest. The gem is very well put but it asks for definitive route names to behave properly.
In my case, as far as devise is concerned, only the registration process is different so I'm trying to build a structure which looks as follow:
app
controllers
users
admin
registration_controller.rb
guest
registration_controller.rb
session_controller.rb
password_controller.rb
registration_controller.rb
the Admin and Guest controllers inherit from the above registration_controller which inherit's from Devise.
Now, to work properly, Devise needs for instance the names guest_user_password and admin_user_password to create or delete password retrievals. In my case, both are under the same path so I want both names to redirect to the same 'users/password' controller.
More important, and that's why I really wanted the alaising. Is that my views should not care whether it is dealing with Admin and Guest routes when redirecting to password retrieval controller. Both are users so I want to use user_password for both.
Hence my question. :)
Also note that as I wrote it, things works. I'm just trying to get the 'most elegant way' of writing it.
How about putting the alias in your ApplicationController?
class ApplicationController < ActionController::Base
alias_method :route_new, :route_old
helper_method :route_new
Remember that it's new name first, then old name.
The helper_method call is in order to use these in your views and not just controllers.
If you like, you can then place this in an included module called something like RouteAliases
You can add something like this to your routes.rb:
Rails.application.routes.draw do
...
Rails.application.routes.named_routes.tap do |named_routes|
named_routes['new_name'] = named_routes['real_name']
end
end
This will create new_name_path and new_name_url helpers. I have tested this with Rails 5.0.6.

Backend administration in Ruby on Rails

I'd like to build a real quick and dirty administrative backend for a Ruby on Rails application I have been attached to at the last minute. I've looked at activescaffold and streamlined and think they are both very attractive and they should be simple to get running, but I don't quite understand how to set up either one as a backend administration page. They seem designed to work like standard Ruby on Rails generators/scaffolds for creating visible front ends with model-view-controller-table name correspondence.
How do you create a admin_players interface when players is already in use and you want to avoid, as much as possible, affecting any of its related files?
The show, edit and index of the original resource are not usuable for the administrator.
I think namespaces is the solution to the problem you have here:
map.namespace :admin do |admin|
admin.resources :customers
end
Which will create routes admin_customers, new_admin_customers, etc.
Then inside the app/controller directory you can have an admin directory. Inside your admin directory, create an admin controller:
./script/generate rspec_controller admin/admin
class Admin::AdminController < ApplicationController
layout "admin"
before_filter :login_required
end
Then create an admin customers controller:
./script/generate rspec_controller admin/customers
And make this inhert from your ApplicationController:
class Admin::CustomersController < Admin::AdminController
This will look for views in app/views/admin/customers
and will expect a layout in app/views/layouts/admin.html.erb.
You can then use whichever plugin or code you like to actually do your administration, streamline, ActiveScaffold, whatever personally I like to use resourcecs_controller, as it saves you a lot of time if you use a REST style architecture, and forcing yourself down that route can save a lot of time elsewhere. Though if you inherited the application that's a moot point by now.
Do check out active_admin at https://github.com/gregbell/active_admin.
I have used Streamlined pretty extensively.
To get Streamline working you create your own controllers - so you can actually run it completely apart from the rest of your application, and you can even run it in a separate 'admin' folder and namespace that can be secured with .
Here is the Customers controller from a recent app:
class CustomersController < ApplicationController
layout 'streamlined'
acts_as_streamlined
Streamlined.ui_for(Customer) do
exporters :csv
new_submit_button :ajax => false
default_order_options :order => "created_at desc"
list_columns :name, :email, :mobile, :comments, :action_required_yes_no
end
end
Use https://github.com/sferik/rails_admin.

Resources