Rails CanCan: reference to resource_instance from application_controller - ruby-on-rails

I have a bunch of controllers that extend the ApplicationController and each one loads a different cancan resource, e.g. UsersController -> #user, PostsController -> #post. I was wondering, is it possible to reference the resource from the ApplicationController without knowing the instance variable name? Something like resource_instance.

Ok, I got my final answer and it's No. I explored cancancan, cancan's well maintained spiritual child and the code is there, but is not meant to be used by the end user.
You could get what you want, it isn't pretty:
self.class.cancan_resource_class.new(self).send(:resource_instance)
That said, please don't. cancan_resource_class isn't documented to to be used by end users and :resource_instance is protected, hence the send. The developers could choose to change this and break your application.
If all have a company, they belong too, I recommend you nest the routes, so all routes have a :company_id you could use to get the #company object you want: companies/:company_id/users, etc.

Related

Dynamic Routes Rails 4, taken from db

Frustrating, I can't find an eligible solution for my problem.
In my Rails 4 app, I want to give my users the possibility to add their own custom post types to their sites. Like:
www.example.com/houses/address-1
www.example2.com/sports/baseball
Both would work, but only for the linked sites. Sports and houses would be the (RESTful) post types, taken from the db, added by users.
I have been struggling to find a elegant solution to accomplish this. I found http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4 but that feels kinda hacky and I'm not sure if reloading the routes works in production, I'm getting signals that it won't.
I'd say I have to use routes constraints http://guides.rubyonrails.org/routing.html#advanced-constraints but I don't have a clue how to approach this.
To be clear, I have no problem with the site setting stuff, the multi tenancy part of my app is fully functional (set in Middleware, so the current site is callable in the routes.rb file). My issue is with the (relative) routes, and how they could be dynamically set with db records.
Any pointers much appreciated.
I think route constraints don't work for you because your domain is a variable here. Instead, you should be examining the request object.
In your ApplicationController, you could define a method that would be called before any action, like so:
class ApplicationController < ActionController::Base
before_action :identify_site
def identify_site
#site = Site.where(:domain => request.host).first
end
end
As you scale, you could use Redis for your domains so you're not making an expensive SQL call on each request.
Then you can just add the #site as a parameter to whatever call you're making. I'm assuming you're doing some sort of "Post" thing, so I'll write some boilerplate code:
class PostController < ApplicationController
def show
#post = Post.where(:site => #site, :type => params[:type], :id => params[:id])
end
end
Just write your routes like any other regular resource.

Displaying different views for the same controller, based on the route

I am building an admin interface for a website. I have certain controllers that have admin functions/views that also have user facing views. For example, when a user goes to /blog, it should show the title, date, first paragraph, etc. of each blog post with links to read the whole post. If an admin goes to admin/posts they would see a list of just the blog post titles, how many comments, edit/delete links, link to create a post, etc.
How would I accomplish this? My (simplified) routes files is this:
namespace :admin do
resources :posts
end
Do I need to have separate controllers?
Usually when using namespaces you want your code to be namespaced as well. I would go for 2 different controllers serving 2 different views.
app/controllers/posts_controller.rb
app/controllers/backend/posts_controller.rb
or
app/controllers/posts_controller.rb
app/controllers/admin_area/posts_controller.rb
You get the idea. I would do the same thing with the views.
You controllers would look like this:
class PostsController < ApplicationController
end
class Backend::PostsController < BackendController
end
class BackendController < ApplicationController
end
There are quite a few ways you could approach this, I can't really think of one being "right" over the other. For simplicity's sake, I'll offer one quick solution, though, admittedly, it's a shortcut.
Presumably, you have a logged in user going to this /admin route, and the current_user would be authorized as an admin, so you can use that to your advantage in your show method.
if current_user.admin?
render 'admin_show'
else
render 'show'
Your views, of course, would render the different implementations.
If you are going to have a number of differences in what the methods do, though, it may be worth creating a new admin_posts_controller. But if there are only a couple differences, then that could be good enough.
Meh, hope that helps.

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.

method_missing and association_proxy in rails

So, here's my problem. I currently am building a simple authentication system for a rails site. I have 3 classes for this: Person, Session, and Role. In my Person model I have defined method_missing to dynamically capture roles according to this guide.
In my application_controller I have some logic to deal with logins and log-outs, the result of which gives me the currently logged in user via:
#user = #application_session.person
Where #application_session is the current session
Now in one of my controllers, I don't want anyone to be able to do anything unless they are an admin, so I included:
before_filter #user.is_an_admin?
This raises a NoMethodError, even though I have method_missing defined in my model. I tried defining is_an_admin?, having it always return true as a test, and that works.
According to this question, I think the problem might have something to do with proxy associations. When I run:
puts #user.proxy_owner
I get a session object, since each user (Person) can have many sessions, and I got my user (Person) from the current session.
I am very confused why #user.is_an_admin? is not calling the method_missing method in my Person controller. Please let me know if you need more information or code snippets.
I am using Rails 3 on Ruby 1.9
I'd consider a method_missing an overkill for such task.
Now, if you have Session class, which belongs_to User, then you can have this:
class Session
belongs_to :user, :extend => PermissionMixin
end
class User
include PermissionMixin
end
module PermissionMixin
def admin?
if cond
true
else
false
end
end
end
P.S. Check cancan, perhaps it'll suit your needs better.
I use a similar permissions check in my system to check the User > Role > Permissions association:
User.current_user.can_sysadmin?
In my controllers I have to instead use:
User.current_user.send('can_sysadmin?')
This may work for you as well.
I have solved this by moving the method_missing method to my application_controller.rb. I change the logic of the method a little to check for a user, and if found, dynamically check the role. If things were not kosher, I had the method redirect to root_url or return true if the user matched the requested roles.
Finally, in my reports controller, I used before_filter :is_an_admin? and got my desired results. However, I am still unclear as to why method_missing had to be defined in my application controller as opposed to directly in the Person (aka #user) model?

Cancan authorization in Non RESTful controller

I have a controller like this, but no model class backing it.
class UserInviteController < ApplicationController
def process_email
authorize! :process_email, :abc
...
method body
...
end
end
In my ability class, I have
can :process_email, :abc if user.role == 1
I am following the wiki Non RESTful Controllers authorization by Cancan and it says that you can pass a symbol as the second argument to both authorize! and can.
It doesn't have to be a model class or instance.
Generally the first argument is the "action" one is trying to perform
and
the second argument is the "subject" the action is being performed on. It can be anything.
I have done the same, but still It doesn't work.
NOTE: I also tried doing it the other way round by
authorize_resource :class => false
as said in the wiki, but that also doesn't work.
If its of any help, my controller doesn't have any actions like show, new, create or any other basic CRUD actions.
Cancan: 1.4.1
I tried to debug following the link Debugging Abilities and figured out what was wrong.
The abilities were correct, I was doing a small mistake by specifying them in the wrong order.
For others coming on to this page, if you face any problems with Cancan abilities not working, try to debug using the above link. You will eventually figure out whats not working and why.

Resources