Rails 5 Multiple Devise Models using same controllers - architecture - ruby-on-rails

I currently have a rails 5 app that has 3 Devise Models and hence 3 separate logins. This is working well.
The challenge I am having is that all three models share the majority of the same resources, however with different authorisation levels. For example, one Devise model may be allowed new and create actions but not destroy etc etc.
Currently, I have controllers namespaced within each Devise model in controllers. So, for example, controllers > admins > same_resource_controller.rb and controllers > seats > same_resource_controller.rb. These files share a lot of the same code and it's creating unnecessary maintenance that I don't feel should be there.
So, If I was to use the controllers > same_resource_controller.rb at a top level I can foresee some fundamental issues:
Authentication, can I put a authenticate_user! in the controller that reads from application_controller:
def authenticate_user
authenticate_admin! || authenticate_seat! || authenticate_client!
end
current_user, is it ok to have, and use, a method like this in the application_controller.rb?
def current_user
current_admin || current_seat || current_client
end
The resulting potential issue is associating records, for example when querying, current_user.another_model.yet_another_model is not the same for and Admin as it is for a Seat.
I really like the idea of cutting down my controllers and tests and associated files by a third but I'm really unsure as to the correct architecture.
If anyone has been through the same or has any good links (I have struggled to find articles on dealing with controllers when multiple Devise models) I would really appreciate some help! Thank you!

Do they all care the same underlying table? At first sight, my question would be why not use a single resource, perhaps with type attribute if you need to flag the client, seat or admin. Are you using CanCan, Pundit, or another community-supported authorization framework?

Related

Using 1 Controller in 2 layout

I have a logic question and I cannot figure out how to do it. First of all, I am working on an social networking site, and I completed the site in pure PHP, but now I am re-writing the backend in rails.
My questions is, I generated UsersController, it has new, create, show, edit, update, delete, and destroy.
I thought I could use "new" to display sign up page, "create" to process sign up, "show" to display profile page, "edit" to display account settings and "update" to process edit.
I might have a logic problem here, maybe I should put "new" and "create" in a signup controller. This where I get confused. The problem with the first logic I said, I have 2 layouts, one of them is for before login, and the other one is for after login. (You can imagine Facebook's header before login, and after login).
So, when I have 2 different layout, I cannot use 1 controller in 2 layout. Because sign up page has "before login header design" and account settings and profile has "after login header design". And as you can guess I define layout in controller.
I don't know if I explained well. Thank you.
By default, Rails will look-up a layout with the same name as the controller, or else application.html.erb. But you can also specify one controller-wide (which won't help you, but bear with me)
class SomethingController
layout "some_name"
...
That's still layout-wide, so not what you need.
But you can also specify a specific layout on each call to render in an action:
def edit
#some logic
render "some_template", :layout => "some_layout"
end
Or, to take the default template lookup, but still specify a layout:
def edit
# some logic
render :layout => "some_layout"
end
There's another way you can specify layouts too, which might be especially appropriate for the use case of "one layout if not logged in, another if logged in":
class SomeController
layout :method_name_to_determine_layout
# ... actions ...
protected
def method_name_to_determine_layout
if current_user
"logged_in_layout_name"
else
"not_logged_in_layout_name"
end
end
You can learn more about the different ways to specify layouts in the Layouts and Rendering Rails Guide
Hope this helps.
Rails has basic CRUD default actions. Additionally each action can have different processing depending on the HTTP verb. You can also add custom actions & routes.
It is best to follow standard Rails practices for each default action. For example, "new" action should route to the form to create a new user when accessed via GET. An HTTP POST to the form should route to the "create" action.
If you need to add an additional controller action, do so with a custom method. Again, I stress, simple CRUD actions should follow normal Rails conventions.
Read more about routing
Read this guide many times to understand simple CRUD actions in Rails
Instead of using 1 controller in 2 layouts, I decided to use separate controllers. So, I have profile_controller, which has "edit" and "update" for account settings and "show" to display profile. And I also users_controller, which has followings: login, login_attempt, signup, signup_attempt, etc..
So, I am not putting signup and edit together in 1 controller, instead using 2 different controllers is much better and clean, I guess.
Sounds like you're trying to roll your own authentication.
I'd recommend using Devise... great tutorial here:
The reason for this is two-fold.
Firstly, Devise gives you the ability to split your app between authenticated and non-authenticated users. Namely, it provides the user_signed_in?, devise_controller? and current_user helpers to aid with this.
This might not appear like a big deal, but it will actually help you with your layouts (I'll describe more in a second).
Secondly, Devise is pre-rolled. Your questions about how to handle signups and registrations have already been solved. Of course, there's nothing preventing you from making your own authentication (Devise is just built on Warden after all), but it should give you some ideas on how this has been done already.
In regards your original question (about layouts), the other answer is very good (in terms of setting layouts per method etc).
To add to it, I would say that you have to remember that Rails is a series of classes. As such, setting the layout option in the controller is the best way to ensure you're getting the correct one.
Here's Rails explanation on it:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
layout :your_layout
private
def your_layout
devise_controller? || !user_signed_in? ? "new_user" : "application"
end
end
I've found it better to keep your logic as terse as possible. IE only have one set of logic for the layout. We tend to keep it in the ApplicationController, overriding where necessary.
--
Finally, your questions also highlighted a base misunderstanding of Rails. I'm not being disrespectful; I've found the clearer your perception of the framework, the better you can work with it:
You have to remember several key points with Rails:
It's an MVC (Model View Controller) framework
It's built on Ruby; hence is object orientated
maybe I should put "new" and "create" in a signup controller. This where I get confused.
If you take Devise as a model, you'll see that you could treat your controllers as layers of abstraction for objects. That is, as Devise shows us, you can have sessions and registrations controllers.
I'm not advocating you do this exactly, I am trying to show that if you put the focus onto the objects you're working with, it becomes clearer where your controller actions should be placed.
You'll also be best understanding the CRUD (Create Read Update Destroy) nature of Rails controllers & objects. The standard Rails controller is set up as such:
Whilst this is not strict, it does give you another indication as to the structure your controllers should adhere to.
You may know this stuff already!

Routing for Multiple User Roles

I have a user model that differentiates into 6 roles, and I am defining different variables and directing them to different view files (similar format, but different tables of information), but within the same controller action, because they all have similar pages "overview", "profile", "messages", etc.
Now the controller is really messy, and has multiple if/else statements. I believe I should be changing the routes so that each user has its own controller, eliminating the use of the if/else monstrosity that currently invades the controller.
def index
if current_user.admin?
....
end
if current_user.moderator?
....
end
end
Question: How do I perform the routing such that url will be
www.website.com/1/schedule, where 1 = current_user.id, while having different view files rendered from the different controllers?
I am thinking of doing a AdminController and a ModeratorController to handle this, but am not sure how to do the routing, or if indeed this is the best way to do it. Thanks for advice in advance!
I think you are doing it the wrong way , If you have similar pages for different role then I think you are accessing same model for handling different roles.
If this is the case then you should use Gem like Cancan (authorization library for Ruby on Rails).
To address your second concern, I don't think this would be the best approach. Try this - move your logic away from multiple controllers, and keep the logic in the ApplicationController. I do not think that you should separate the different roles to controllers. Instead, look to your User model and put in a method that checks the privilege level for the different users. You could create methods in your User model that you could call in your controllers to see if the user is allowed access to the action. before_action would be recommended here.
If you decide to keep multiple controllers, I recommend a gem like Authority. Makes it much easier to keep track of different privileges, even across different controllers - I think routing is addressed as well: https://github.com/nathanl/authority

Authorization for actions (not models!) with roles in rails

If I've got a simple rails user model that has an array of roles, is it sufficient enough to control access to actions by simply checking the model's role attribute for that role and blocking/proceeding accordingly?
Is there an advanced system that I ought to leverage due to unforeseen complexity?
Important: I'm not looking to authorize users/roles to models (I am already aware of CanCan). I'm looking to do security at the controller level so that I can break out the functionality in finer detail.
Even more important: Seriously, I'm not necessarily asking about CanCan, please read the question carefully and pay attention! :)
Question 1: YES, Question 2: NO.
I just keep this simple
If you check the models attribute in the controller, the controller will restrict all users that do not have this attribute set.
ex:
def create
#user.find(params[:user_id])
if #user.admin?
#post.new(params[:post])
#post.create!
end
end
make a method in the user model
def admin?
role == "Admin"
end
You should make better code than this. To much logic in the controller, but this will keep all, except admins out.

Model-level authorization in Rails

I want to implement authorization in my Rails application on a model level (not controller), in a similar way that validation on models is done. What is the best way to do this?
If it is implemented in the models itself, the main problem is that the models don't have access to the current user. I've seen solutions like: Thread.current[:user_id] = session[:user_id], but that doesn't seem like a good idea.
I've seen a different approach where variants of the methods like create, find and new are created, accepting an additional parameter for the current user.
Another approach would be to implement all the methods in the User/role class, so for example user.posts.create or user.readable_posts.find would be used instead of Post.create or Post.find.
Which of these approaches would be suggested? Are there any better ways to implement the authorization? Are there any plugins that makes this easier? I need an approach that scales well for multiple roles and models.
I would recommend you to look at declarative authorization. It works with both models and controllers.
The way it do what you are asking is having a before_filter in the applicationController that sets Authorization.current_user = current_user where Authorization is a module.
I think that approach is the best one, it keeps the models clean and you don't have to remember to include the user everywhere, but can filter it in the models callback functions instead.
Why would you do this? Isn't controller level identification enough?
if #user.is_able_to_do_this?
do_this
else
blah!
end
... and in your model
def is_able_to_do_this
if self.rights > Constant::Admin_level_whatever
return true
end
return false
end

Using multiple controllers in one view in Rails

I'm working on something like a social networking mesh; I am using different API's from various websites, e.g. Last.FM, Delicious, Twitter, ...
I've created one controller per each website (currently, there are 7).
Sample views:
localhost:3000/lastfm <- All datas i gathered from user's Last.fm account
localhost:3000/twitter <- All datas i gathered from user's Twitter account
...
Now I want to show these datas in one view (localhost:3000/index.hmtl) by using these different controllers.
Components are deprecated, creating one controller and bury all the API's in that seems ugly, too..
So I don't know how to do this. Any idea?
First off, you should put all of the data-storing and data-gathering methods into Resources and Models so they are accessible from all controllers. You can keep the internal data-mutating operations in your individual controllers though. Once you have it organized like this, you could do what Hobo does: create a controller just for the front page, "front_controller" if you will. Here you can display data gathered from all of your models and resources, as well as links to your other controller actions.
These are a few interesting thoughts on better organizing your models and controllers (fat models, skinny controllers is a rule of thumb.
Since you said you are using other API's (like lastfm and twitter), you might want to take a look at this railscasts about creating non ActiveRecord models (models that are not tied to a database)
here is some pseudo code, keep in mind its really only targeted at your question.
# pseudo code
class TwitterController < ApplicationController
def index
#services = {
:twitter => TwitterModel.find(:all, ...),
}
end
def update_twitter
TwitterUpdaterClass.update { |twit|
_m = TwitterModel.new
_m.message = twit.msg
_m.from = twit.from
# ..
_m.save
}
end
end
class MyIndexController < ApplicationController
def index
#services = {
:twitter => TwitterModel.find(:all, ...),
:lastfm => LastFmModel.find(:all, ...)
}
end
end
it might be much better to have background-workers update your rest-services instead a controller which you need to invoke every time you want to fetch recent tweets.
here is nice article showing - 6 ways to run background jobs in rubyonrails
# more pseudo code
class TwitterWorker < BackgrounDRb::MetaWorker
set_worker_name :twitter_worker
def create(args = nil) # instead of TwitterController.update_twitter
TwitterUpdaterClass.update { |twit|
_m = TwitterModel.new
_m.message = twit.msg
_m.from = twit.from
# ..
_m.save
}
end
end
First off, you should put all of the data-storing and data-gathering methods into Resources and Models so they are accessible from all controllers. You can keep the internal data-mutating operations in your individual controllers though. Once you have it organized like this, you could do what Hobo does: create a controller just for the front page, "front_controller" if you will. Here you can display data gathered from all of your models and resources, as well as links to your other controller actions.
I think you should read a bit about rails' MVC architecture.
It seems to me that you are neglecting the M(odel) part of it. The models should hold the data, thus being the most important part of your application.These are a few interesting thoughts on better organizing your models and controllers (fat models, skinny controllers is a rule of thumb. Since you said you are using other API's (like lastfm and twitter), you might want to take a look at this railscast about creating non ActiveRecord models (models that are not tied to a database)
If you also provide an API for your users, I suggest using a RESTful approach, as it can really be easy to develop and maintain once you get the hang of it. You should read more about resources, as your localhost/lastfm and localhost/twitter are resources and not views.
Hope this helps. Good luck

Resources