I am setting up Devise-Basecamper to enable subdomain scoped authentication to extend Devise's usefulness even more. The gem might be a bit old but it seems like an ideal solution if I get everything up and running. The README.md is super clear but the only thing that threw me off is a bit of code relating to Mongoid even though I am using ActiveRecord. If you could help me write this code according to ActiveRecord I would be very grateful. I have a model called Account, which is like the company or organization.
Here's the necessary excerpt from Devise-Basecamper's readme. I have put the Mongoid code in >>> and <<<
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :subdomain, :current_account
before_filter :validate_subdomain, :authenticate_user!
private # ----------------------------------------------------
def current_acount
# The where clause is assuming you are using Mongoid, change appropriately
# for ActiveRecord or a different supported ORM.
>>>#current_account ||= Association.where(subdomain: subdomain).first<<<
end
def subdomain
request.subdomain
end
# This will redirect the user to your 404 page if the account can not be found
# based on the subdomain. You can change this to whatever best fits your
# application.
def validate_subdomain
redirect_to '/404.html' if current_account.nil?
end
end
First of all, there is a typo in current_account method. It's current_account not current_acount.
so replace this line
def current_acount
with
def current_account
Second, You have change Association to Account because Account is actually Model.
Replace this line
#current_account ||= Association.where(subdomain: subdomain).first
with
#current_account ||= Account.where(subdomain: subdomain).first
Third, If you're not using mongoid then you have change where clause. e.g.
replace where clause
#current_account ||= Association.where(subdomain: subdomain).first
with
#current_account ||= Association.where("subdomain = ?", subdomain).first
Final code is here..
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :subdomain, :current_account
before_filter :validate_subdomain, :authenticate_user!
private # ----------------------------------------------------
def current_account
# The where clause is assuming you are using Mongoid, change appropriately
# for ActiveRecord or a different supported ORM.
#current_account ||= Account.where("subdomain = ?", subdomain).first
end
def subdomain
request.subdomain
end
# This will redirect the user to your 404 page if the account can not be found
# based on the subdomain. You can change this to whatever best fits your
# application.
def validate_subdomain
redirect_to '/404.html' if current_account.nil?
end
end
Related
I am returning to Rails after 5 years out of it and trying to understand the changes. I am going through Ryan Bates' Railscasts looking to update a template I built some years ago and am getting the above error when initializing the permissions class for authentication. (See RC#386 about 18:00 into the playback.)
Rails has changed the before_filter to before_action (makes sense...) and I have the following in the ApplicationController:
class ApplicationController < ActionController::Base
before_action :authorize
delegate :allow?, to: :current_permission
helper_method :allow?
delegate :allow_param?, to: :current_permission
helper_method :allow_param?
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def current_permission
#current_permission ||= Permissions.permission_for(current_user)
end
def current_resource
nil
end
def authorize
if current_permission.allow?(params[:controller], params[:action], current_resource)
current_permission.permit_params! params
else
redirect_to root_url, alert: "Not authorized."
end
end
end
My permissions.rb file has the following:
module Permissions
def self.permission_for(user)
if user.nil?
GuestPermission.new
elsif user.admin?
AdminPermission.new(user)
else
MemberPermission.new(user)
end
end
end
I'm getting the above error: NoMethodError at /undefined method "permission_for" for Permissions:Module from BetterErrors (and Puma). However the method should be defined in the Permissions module; it's right there. Yet somehow, something has changed in Rails that I can't figure out.
I have tried to require the file: nothing.
Any help?
You need to include a module in a class to use it. It then adds functionality to that class, you do not access methods in module itself.
I'm guessing you need to do something like this (not tested):
class ApplicationController < ActionController::Base
include Permissions
before_action :authorize
change your module to:
module Permissions
def permission_for(user)
...
and then permissions_for is available in your controller
def current_permission
#current_permission ||= permission_for(current_user)
end
The only thing that has changed in Rails is how folders and files are included. The lib folder inside your rails app is not autoloaded by default.
I think best practice is now to add a lib folder within the app folder and then put all your modules and other classes in there. They will be included as soon as you restart your server.
I am attempting to pass thru request data to the Ability model as suggested here:
class ApplicationController < ActionController::Base
#...
private
def current_ability
#current_ability ||= Ability.new(current_user, request.remote_ip)
end
end
and here:
class Ability
include CanCan::Ability
def initialize(user, ip_address=nil)
can :create, Comment unless BLACKLIST_IPS.include? ip_address
end
end
See: https://github.com/ryanb/cancan/wiki/Accessing-request-data
However, I am using ActiveAdmin with the CancanAdapter, and it uses a separate initialize call via:
def initialize_cancan_ability
klass = resource.namespace.cancan_ability_class
klass = klass.constantize if klass.is_a? String
klass.new user
end
See: https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/cancan_adapter.rb
So how/where can I redefine initialize_cancan_ability so that I can pass in request data similar to the current_ability example?
Basically I'm hoping to just replace the last line as such:
klass.new user, request
Thanks.
You can create a file under lib/monkey_patches/active_admin.rb and put your overridden method there:
require 'cancan'
# Add a setting to the application to configure the ability
ActiveAdmin::Application.inheritable_setting :cancan_ability_class, "Ability"
module ActiveAdmin
private
def initialize_cancan_ability
klass = resource.namespace.cancan_ability_class
klass = klass.constantize if klass.is_a? String
klass.new user, request
end
end
end
If you use Devise, you can access the the Ip from the User model user.current_sign_in_ip
I'm allowing my users to have multiple profiles (user has many profiles) and one of them is the default. In my users table I have a default_profile_id.
How do I create a "default_profile" like Devise's current_user which I can use everywhere?
Where should I put this line?
default_profile = Profile.find(current_user.default_profile_id)
Devise's current_user method looks like this:
def current_#{mapping}
#current_#{mapping} ||= warden.authenticate(:scope => :#{mapping})
end
As you can see, the #current_#{mapping} is being memoized. In your case you'd want to use something like this:
def default_profile
#default_profile ||= Profile.find(current_user.default_profile_id)
end
Regarding using it everywhere, I'm going to assume you want to use it both in your controllers and in your views. If that's the case you would declare it in your ApplicationController like so:
class ApplicationController < ActionController::Base
helper_method :default_profile
def default_profile
#default_profile ||= Profile.find(current_user.default_profile_id)
end
end
The helper_method will allow you to access this memoized default_profile in your views. Having this method in the ApplicationController allows you to call it from your other controllers.
You can put this code inside application controller by defining inside a method:
class ApplicationController < ActionController::Base
...
helper_method :default_profile
def default_profile
Profile.find(current_user.default_profile_id)
rescue
nil
end
...
end
And, can access it like current_user in your application. If you call default_profile, it will give you the profile record if available, otherwise nil.
I would add a method profile to user or define a has_one (preferred). Than it is just current_user.profile if you want the default profile:
has_many :profiles
has_one :profile # aka the default profile
I would not implement the shortcut method, but you want:
class ApplicationController < ActionController::Base
def default_profile
current_user.profile
end
helper_method :default_profile
end
I have the following application_controller method:
def current_account
#current_account ||= Account.find_by_subdomain(request.subdomain)
end
Should I be calling it using a before_filter or a helper_method? What's the difference between the two and what should I consider in terms of the trade-offs in this case?
Thanks.
UPDATE FOR BETTER CLARITY
I'm finding that I can user the before_filter instead of the helper_method in that I'm able to call controller defined methods from my views. Perhaps it's something in how I arranged my code, so here is what I have:
controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
include SessionsHelper
before_filter :current_account
helper_method :current_user
end
helpers/sessions_helper.rb
module SessionsHelper
private
def current_account
#current_account ||= Account.find_by_subdomain(request.subdomain)
end
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def logged_in?
if current_user
return true
else
return false
end
end
end
controllers/spaces_controller.rb
class SpacesController < ApplicationController
def home
unless logged_in?
redirect_to login_path
end
end
end
views/spaces/home.html.erb
<%= current_account.inspect %>
In theory, this shouldn't work, right?
There is no relationship between using before_filter or helper_method. You should use helper method when you have a method in your controller that you would like to reuse in your views, this current_account might be a nice example for helper_method if you need to use it in your views.
They are two very different things. A before_filter is something that you want to be called once before an action starts. A helper method on the other hand gets repeated often, typically in a view.
That method you have there is just fine to stay where it is.
I solved my problem. I'm new to Rails, and didn't know that methods defined in the helpers directory are automatically helper_methods. Now I'm wondering how this effects memory/performance. But at least I have the mystery solved. Thanks everyone for your help!
I working on an app with user authorization. It has a List and User classes. The authentication was built with Ryan Bates http://railscasts.com/episodes/270-authentication-in-rails-3-1
I'm not sure about authorization process. I read about cancan gem. But i could not understand.
I want to achieve this:
User only able to view/edit/delete his own list.
User only able to view/edit/delete his own profile(user class).
I don't implement user level right now. No guess or admin.
How to use before_filter method in list and User controller with current_user instance?
Since you are defining current_user in the application controller, this is easy. You can use before_filter like this in the Users controller:
class ItemsController < ApplicationController
before_filter :check_if_owner, :only => [:edit, :update, :show, :destroy]
def check_if_owner
unless current_user.admin? # check whether the user is admin, preferably by a method in the model
unless # check whether the current user is the owner of the item (or whether it is his account) like 'current_user.id == params[:id].to_i'
flash[:notice] = "You dont have permission to modify this item"
redirect_to # some path
return
end
end
end
###
end
You should add a similar method to UsersController to check if it is his profile, he is editing.
Also, have a look at Devise which is the recommended plugin for authentication purposes.
For this I'd not use devise. It's way to much for this simple use.
I'd make a seperate controller for the public views and always refere to current_user
Remember to make routes for the actions in the PublicController
class PublicController < ApplicationController
before_filter :login_required?
def list
#list = current_user.list
end
def user
#user = current_user
end
def user_delete
#user = current_user
# do your magic
end
def user_update
#user = current_user
# do your magic
end
# and so on...
end