I am following Ryan Bate's Multitenancy with Scopes tutorial (http://railscasts.com/episodes/388-multitenancy-with-scopes) and I ran into the following error when I attempted to scope my users to the correct tenant:
undefined method users for #<Tenant:0x007fc61f075228>
The error references my show action in my users_controller.rb:
def show
#user = current_tenant.users
end
This occurred because I removed the relationship in Tenants.rb to add cattr_accessor :current_id
class Tenant < ActiveRecord::Base
cattr_accessor :current_id
#has_many :users
def self.current_id=(id)
Thread.current[:tenant_id] = id
end
def self.current_id
Thread.current[:tenant_id]
end
end
In my user.rb, I added the default_scope:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
default_scope { where(tenant_id: Tenant.current_id) }
end
Here is my applications_controller.rb:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
around_filter :scope_current_tenant
def after_sign_in_path_for(resource_or_scope)
user_show_path(current_user.id)
end
private
def current_tenant
Tenant.find_by_subdomain! request.subdomain
end
helper_method :current_tenant
def scope_current_tenant
Tenant.current_id = current_tenant.id
yield
ensure
Tenant.current_id = nil
end
end
I am not sure why Ryan removed the has_many :users relationship, but I assume it is to prevent users with different tenants to view each other.
How do I handle and fix this error?
It seem like you do not need use
current_tenant.users
It scoped request to db automatic (uses default scope in user model).
Just
#users = Users.all
Related
I'm trying to set up an age restriction when a user registers so that if they are too young they cannot register on an app i'm building.
I had to over-ride devise to allow me to pass through other values to the user (like :birth_date). However I also want to check the age of the user so that if they are too young, they cannot use the app.
What I have right here, in a rudimentary way it works, but it is not quite what I would like.
<%= f.input :birth_date, required: true, start_year:1999 %>
In my user model I created some methods that address the problem, however ultimately my problem is that none of this code is getting hit during the registration process, and that is what I need some help with. If someone could take a look at point me in the right direction, I would greatly appreciate it!
class User < ApplicationRecord
validate :age_restriction
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :cafes
def age_restriction
if (birth_date.to_date + 18.years) < Date.today # assuming dob format is mm/dd/yy
errors.add :birth_date, 'must be older than 18'
end
end
end
The controller I used to over-ride devise I called registration_controller and it is like so
class RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: :create
before_action :configure_account_update_params, only: :update
protected
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:birth_date])
end
def configure_account_update_params
devise_parameter_sanitizer.permit(:account_update, keys: [:bith_date])
end
end
My initial controller was my user_controller. Initially I was hoping this would solve my issue, but after some more work realized I needed to over-ride devise (hence the other registrations_controller). I'll admit this may be what is causing me my issue, not sure though.
class UsersController < ActiveRecord::Base
def show
#user = User.find(params[:id])
end
def create
#user = current_user.build(user_params)
#user.save
end
private
def user_params
params.require(:user).permit(:birth_date)
end
end
Use validations.
There is a gem which adds some useful date validators:
https://github.com/adzap/validates_timeliness/
validates_date :date_of_birth, :before => lambda { 18.years.ago },
:before_message => "must be at least 18 years old"
You can use model validations to prevent a user instance from being created if the user does not meet the age restriction you have set:
User.rb
validate :age_restriction
def age_restriction
if (birth_date.to_date + 18.years) < Date.today # assuming dob format is mm/dd/yy
errors.add :birth_date, 'must be older than 18'
end
end
I'm trying to use before_action :authenticate_admin! to determine if the current user is an admin or not. I am using devise with single table inheritance (STI).
Here is my User model;
class User < ApplicationRecord
def admin?
false
end
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable, :lockable
end
And my Admin model;
class Users::Admin < User
def devise_scope
:admin
end
def admin?
true
end
end
Just to test out devise, I have this in my routes file;
authenticated :user, ->(u) { u.admin? } do
resources :teams
end
And indeed, if I am signed in as a different User type (so not Admin), I can't get to that resource.
But this fails to work;
class Teams < ApplicationController
before_action :authenticate_admin!
def index
end
end
I get the error;
undefined method `authenticate_admin!'
Quite new to Rails so I don't understand how to resolve this.
UPDATE
I had to put this in the routes file;
devise_for :admins, class_name: 'Users::Admin'
Previously I only had;
devise_for :users, controllers: {sessions: 'users/sessions'}
Done a lot of reading on the situation below. Want some opinions.
In the thread at (1) the user asks "How to create another object when creating a Devise User from their registration form in Rails?". This is exactly what I am trying to do as well. When a user registers I want to create an Account object.
(1) How to create another object when creating a Devise User from their registration form in Rails?
However there's a problem I've been having, which also surfaced at the referenced thread. When I run the following in my subclassed registrations controller. I get "When assigning attributes, you must pass a hash as an argument." ArgumentError in RegistrationsController#create.
class RegistrationsController < Devise::RegistrationsController
after_action :create_account, only: [:create, :update]
private
def create_account
Account.create(#user)
end
end
It turns out that using callbacks don't work because I don't have access to the params hash. I've tried so many different variations of the Account.create(current_user) or (#user) as well as adding another def below to try to permit the user id param to go to user_id foreign key of Account. Still the same error "When assigning attributes, you must pass a hash as an argument."
def account_params
params.require(:account).permit(:user_id,:user)
end
This is where the question at (1) ended up. It proposed bypassing Devise's RegistrationsController.
Is this the only remaining way to achieve the objective of creating other models when a User registers in Devise? Any other suggestions? I don't want to commit to bypassing the registrations controller unless its the only way.
Thanks for any suggestions and happy to provide any other information.
class Account < ApplicationRecord
has_many :users
end
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :projects
has_many :notes
belongs_to :account
In your user.rb file, you could do this:
after_create :create_account
def create_account
#user = User.last
#account = Account.new
#user.account = #account
#user.save
#account.save
end
I am using Devise gem in my Rails project. The reason I use Devise is because I want just logged in user can rate their teachers. Now, I get this error in my ratings_controller.rb although I already added user_id into my ratings and teachers table.
undefined method `current_user' for Teacher:0x00000004fcac48
#rating = #teacher.current_user.ratings.build
Here is my ratings_controller.rb:
class RatingsController < ApplicationController
def new
get_teacher
#rating = #teacher.current_user.ratings.build
end
def create
get_teacher
#rating = #teacher.current_user.ratings.create(rating_params)
if #rating.save
redirect_to school_teacher_path(#teacher.school, #teacher)
else
render 'new'
end
end
def destroy
get_teacher
#rating = #teacher.ratings.find(params[:id])
#rating.destroy
redirect_to school_teacher_path(#teacher.school, #teacher)
end
def get_teacher
#teacher = Teacher.find(params[:teacher_id])
end
private
def rating_params
params.require(:rating).permit(:easiness, :helpfulness, :clarity, :comment,
:teacher_id, :school_id)
end
end
rating.rb:
class Rating < ActiveRecord::Base
belongs_to :teacher
belongs_to :user
end
teacher.rb:
class Teacher < ActiveRecord::Base
belongs_to :school
has_many :ratings, dependent: :destroy
has_many :users
def name
"#{firstName} #{middleName} #{lastName}"
end
def to_s
name
end
end
user.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :ratings
has_many :teachers
end
current_user is a controller helper, it's not a instance method of model.
you can add before_action :authenticate_user! in controller to make sure only logged in user can rate
You can change several code here. Before fixing your code, I suggest you to user before_action to make DRY code.
class RatingsController < ApplicationController
before_action :authenticate_user!
before_action :get_teacher
# your code #
private
def get_teacher
#teacher = Teacher.find(params[:teacher_id])
end
def rating_params
params.require(:rating).permit(:easiness, :helpfulness, :clarity, :comment, :teacher_id, :school_id)
end
end
Then, add before_action :authenticate_user! above before_action :get_teacher so you can get current_user in each method.
Finally, you have to fix your new and create method into this:
def new
#rating = current_user.ratings.build
end
def create
#rating = current_user.ratings.create(rating_params)
if #rating.save
redirect_to school_teacher_path(#teacher.school, #teacher)
else
render 'new'
end
end
You do not need to #teacher.current_user.ratings.create(rating_params) to get teacher_id because you have teacher_id in your rating_params. I hope this help you.
You need to build rating object for user like this:-
#rating = current_user.ratings.build
I'm new to rails and I tried to make simple authentication with anonymous user. I followed this tutorial and I have this error:
undefined method `find_or_initialize_by_token'
This is my AnonymousUser model:
class AnonymousUser < User
ACCESSIBLE_ATTRS = [:name, :email]
attr_accessible *ACCESSIBLE_ATTRS, :type, :token, as: :registrant
def register(params)
params = params.merge(type: 'User', token: nil)
self.update_attributes(params, as: :registrant)
end
end
This is my User model:
class User < ActiveRecord::Base
devise :database_authenticatable, :confirmable, :lockable, :recoverable,
:rememberable, :registerable, :trackable, :timeoutable, :validatable,
:token_authenticatable
attr_accessible :email, :password, :password_confirmation, :remember_me
end
And the last one important is my ApplicationController which has this error:
class ApplicationController < ActionController::Base
protect_from_forgery
def authenticate_user!(*args)
current_user.present? || super(*args)
end
def current_user
super || AnonymousUser.find_or_initialize_by_token(anonymous_user_token).tap do |user|
user.save(validate: false) if user.new_record?
end
end
private
def anonymous_user_token
session[:user_token] ||= SecureRandom.hex(8)
end
end
Someone told me that if AnonymousUser user inherits from User then AnonymousUser have method called find_or_initialize_by_token, but i don't know how to fix it.
Provided you have latest rails installed, try to refactor:
# in ApplicationController#current_user
AnonymousUser.find_or_initialize_by_token(anonymous_user_token).tap do |user|
user.save(validate: false) if user.new_record?
end
to something like this:
AnonymousUser.safely_find(anonymous_user_token)
and push the find_or_initialize_by_token and save(validate: false) into the model.
I wrote the blog post you referenced, but today, I would use
AnonymousUser.where(anonymous_user_token: anonymous_user_token).first_or_initialize
Dynamic finders have been deprecated AFAIK.
However, #Saurabh Jain is absolutely correct in his suggestion to refactor that block into a nice little push-button class method on the AnonymousUser.