Im a RoR rookie and am using rails 3.2.3.
I've been using devise and so far it has been great, however, I've run into a problem.
I have a User table with devise and a HABTM association with a Role table. I have the join table created and everything is fine. When I create a user and choose it's role, it creates the data in the join table correctly.
However, I activated devises' confirmable option and things started to go wrong.
When I create a new user, it no longer inserts the record in the join table as it should.
I mean, all I have literary done was add , :confirmable in front of the other devise options such as :database_authenticatable, :recoverable, :rememberable, :trackable and :validatable.
When I activated :confirmable I wrote this migration (which I saw on stack overflow also):
class AddConfirmableToDeviseV < ActiveRecord::Migration
def change
change_table(:users) do |t|
t.confirmable
end
add_index :users, :confirmation_token, :unique => true
end
end
It sends the email with the link to confirm, nothing wrong with that, but when I click it, the app breaks as that user does not have a role assigned to it, and that is a must.
And as I said, all I did was add :confirmable. If I comment it out like this #,:confirmable in my User model, the role and user data gets inserted in the join table correctly.
What's going on? Any tips?
Thanks in advance,
Regards
Update
#Kyle C
I'm creating the user with the regular actions:
View:
<div class="field">
<%= f.label :username %><br />
<%= f.text_field :username %>
</div>
(...)
<% for role in Role.find(:all) %>
<div class="field">
<%= check_box_tag "user[role_ids][]", role.id, #user.roles.include?(role) %>
<%= role.name %>
</div>
<%end%>
Then in my controller:
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to(#user, :notice => 'User was successfully created.') }
(...)
Without :confirmable, this is enough to enter the data in the join table.
On top of this, I have this in my app controller:
def after_sign_in_path_for(resource)
if current_user.roles.first.id == 1
admin_dashboard_path
elsif current_user.roles.first.id == 2
manage_path
end
end
If i take this out, the user gets logged in when he clicks the confirmation email, however, the middle join table is still doesn't get the association.
I've browsed the documentation (https://github.com/plataformatec/devise/blob/master/lib/devise/models/confirmable.rb)
but I'm still a rookie and I also didn't find anything that would override my app's initial behaviour.
Is there a way to force the input of the records in my join table after I create the user?
I've tried this:
def create
#user = User.new(params[:user])
#role = Role.find(params[:user][:role_ids])
if #user.save
#user.role << #role
#user.save
AND (wrong thing to do but still without success)
(...)
if #user.save
query = ActiveRecord::Base.connection.raw_connection.prepare("INSERT INTO roles_users (role_id, user_id) VALUES (?,?);")
query.execute(#role.id, #user.id)
query.close
This is really frustrating, anyone else came up with this issue when activating :confirmable with a HABTM?
Thanks for all your help
t.confirmable is no longer supported please use this migration
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email
Related
My client is wondering if I can set up a page in ActiveAdmin that would allow them to change the message (edit) a registration Mailer view template.
Example of where it would be
Is there any way this can be done? I've looked around but am really struggling to find anything remotely similar to this topic.
Yes, you can do it. I will tell you the method to create a better way to handle all such email templates from the Active Admin Dashboard.
1- Create a model named as email_template.rb by running rails g model email_template
2- Now add the below code to db/migrate/timestamp_create_email_templates.rb
def change
create_table :email_templates do |t|
t.string :name
t.text :subject
t.text :body
t.timestamps
end
add_index :email_templates, [:name], unique: true
end
3- Then run rake db:migrate
4- Then run rails generate active_admin:resource EmailTemplate to add the Email Template to the Active Admin dashboard.
5- Paste the Below code to the admin/email_template.rb
ActiveAdmin.register EmailTemplate do
actions :all, except: [:destroy]
permit_params :name, :subject, :body#, :image
form do |f|
f.inputs 'Details' do
f.input :name
f.input :subject
f.input :body
end
f.actions
end
end
6- Then navigate to the Email Templates from the Active admin Dashboard to create a new Registration Mailer template, assuming named as welcome_email
7- Add Subject and Body of the Template from the Active Admin Dashboard.
Note - The subject of the Email Template can be added as plain HTML like the below screenshot:
8- I am assuming that you already have working mail functionality in your application and also have any of the mailers under the mailers folder like user_mailer.rb. If not then please add a mailer first, it'll help you a lot in the future too. You can follow this link for any help in mailers.
9- Add a function that would be called when the welcome_email will be called to send any of the User under mailers/user_mailer.rb like this:
class UserMailer < ApplicationMailer
before_action { #user = params[:user] }
def welcome_email
email = EmailTemplate.find_by_name("welcome_email")
mail(:to => #user.email,
:subject => email.subject,
:body => ERB.new(email.body).result_with_hash(user: #user),
content_type: "text/html"
)
end
end
10- Now, you just need to call the above function where do you need the send the mail functionality like in the controllers/users/registrations_controller (I am using the modified devised controller) like this:
def create
super do |user|
if user.save
UserMailer.with(user: user).welcome_email.deliver
end
end
end
As the same above you can add multiple Email Templates and handle all of them from the Active Admin Dashboard and call them wherever you need them by adding a function to the mailer.
I am trying to create a secure login that will require a username and password. I am struggling with this and can not figure out the problem. When I try to access my login page I receive an error for <%= form_tag login_url do %> in the login page. If anyone can help me make this work, you will be my hero.
Schema for table that holds username and password
create_table "users", force: true do |t|
t.string "name"
t.string "password_digest"
t.string "password"
t.string "password_confirmation"
t.datetime "created_at"
t.datetime "updated_at"
end
Controller from the table that holds username and password
class User < ActiveRecord::Base
has_secure_password
end
The login page (which I can not get to work)
<h2>The Maintenance Functions are restricted to authorized users. Please login below</h2>
<%= form_tag login_url do %>
<p>
<label for="name">Name:</label>
<%= text_field_tag :name, params[:name] %>
</p>
<p>
<label for="password">Password:</label>
<%= password_field_tag :password, params[:password] %>
</p>
<%= submit_tag "login" %>
<% end %>
The controller for the login page
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_name(params[:name])
if user and user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to secertin_secertout_path :notice => "Logged in successfully"
else
flash.alert = "Invalid userid/password combination"
render 'new'
end
end
def destroy
session[:user_id] = nil
redirect_to root_url_path :notice => "Logged Out"
end
end
You'll be best using Devise (or maybe Authlogic)
Reason being it's sometimes better to outsource something as important as user authentication. Why? Several reasons:
it's a vital part of your app
it needs to adapt with changes in HTTP security
it needs to work with other dependencies
If you roll your own authentication (which isn't that difficult), your major issue is you won't have the extensibility or reliability that one of these solutions provides. I would highly recommend using devise - it's doing exactly what you need anyway
Remove the password and password_confirmation columns from your table and your code should work. has_secure_password gives you the virtual attributes password and password_confirmation but it stores a hashed version of the password in your table in the password_digest column. Not storing the plain text version of the password in the database is part of what makes has_secure_password secure.
I'm building a website with user authentication. And I just noticed that if I create a user with an existing email, it just doesn't work which is normal, but I'd like to give feedback to the user. So for that I need to determine if any user has already that email.
I've tried some things like:
if User.email.include? params[:user][:email]
flash.now[:error] = "A user with this password already exists"
render :action => :new, :layout => 'signin-layout.html.erb'
Those are the columns for User:
2.1.0 :014 > User.column_names
=> ["id", "name", "email", "created_at", "updated_at", "password_digest", "remember_token", "admin", "team_id", "teamLeader"]
And the result I get is a big fat error:
undefined method `email' for #<Class:0x00000102b9a908>
So if anybody sees what I'm doing wrong, or knows another way to do it, that would be great.
Cheers
Try this:
if User.exists?(:email => params[:user][:email])
flash.now[:error] = "A user with this password already exists"
render :action => :new, :layout => 'signin-layout.html.erb'
...
else
# more code here..
end
Also, you can add validations when you're creating the object:
class User
validates_uniqueness_of :email
More on different validations here: http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
I believe this way of doing the validation is wrong, you should validate the uniqueness of the email in the User model itself like below
validates :email, uniqueness: true #User model
This way the validation would be on the the User model. The problem with the condition you are using is that it is accessing an instance method specific to objects as a class method. So User.email means that there is a method called email that has the same logic for all the instances of the user class or more formally a class method which you don't have here. The email is an attribute specific to each user an instance attribute/variable (Each user has a different email).
You can see/show the validation errors present on the model using #user.errors.full_messages where #user is the instance you are trying to register/save.
This is how I would normally do it if this action is for registering users i.e. creating new users.
class User < ActiveRecord::Base
#attribute accessors and accessible
validates :email, uniqueness: true
end
class UsersController < ApplicationController
def create
#user = User.new params[:user]
if #user.save
#code for redirect or rendering the page you want
else
render 'new'
end
end
end
#new.html.erb
<%= form_for #user do |f| %>
<% if #user.errors.any? %>
<div>
<ul>
<% #job.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
#form fields
<% end %>
This way you display all the error messages to the user at the top of the registration form.
I followed railscast #250 Authentication from Scratch & got everthing wworking fine. Now I'm trying to only display edit & destroy links on my index page to admin user's.
I've set up mu User database with a admin boolean field & tried putting a simple if statement in the view of another model (hikingtrails) to only display certain links to admin users but I get this error when I try it out, undefined method 'admin?' for nil:NilClass
Database Schema
create_table "users", :force => true do |t|
t.string "email"
t.string "password_digest"
t.boolean "admin"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
User Model
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation#, :admin
validates :email, :uniqueness => true
has_secure_password
end
Application Controller
class ApplicationController < ActionController::Base
protect_from_forgery
# fetch the currently logged-in user record to see if the user is currently logged in
# putting this method in ApplicationController so that it’s available in all controllers
private
def current_user
# checks for a User based on the session’s user id that was stored when they logged in, and stores result in an instance variable
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
# to give access to this method from all the views, the helper_method makes it a helper method
helper_method :current_user
# basic authorization, user must be logged in!
def authorize
redirect_to login_url, alert: "You must be logged in to perform this action" if current_user.nil?
end
end
views/hikingtrails/index.html.erb
<% if current_user.admin? %>
<%= link_to t('.edit', :default => t("helpers.links.edit")),
edit_hikingtrail_path(hikingtrail), :class => 'btn btn-mini' %>
<%= link_to t('.destroy', :default => t("helpers.links.destroy")),
hikingtrail_path(hikingtrail),
:method => :delete,
:data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) },
:class => 'btn btn-mini btn-danger' %>
<% end %>
current_user will be nil if a user is not logged in according to your code. So you need to do this:
<% if current_user && current_user.admin? %>
or using the try method Rails adds to all objects.
<% if current_user.try(:admin?) %>
as Dogbert said, current_user will be nil if the user is not logged in.
I would suggest two other alternatives:
1) in the current_user method return a special type "guest" user instead of nil. Il will be useful in case you want to do something else with it later, for example in response to some user action.
As inspiration, look at how Ryan Bates explains the Ability class of his gem cancan: link.
The first thing he does is creating an unitilized (and not persisted in DB) user. An that Ability class will be instantiated each time Rails will parse an ERB template with that kind of user verification.
So, you could do:
def current_user
#current_user ||= ((User.find(session[:user_id]) if session[:user_id]) || User.new)
end
So, if (User.find(session[:user_id]) if session[:user_id]) returns nil, the #current_user will be set to an uninitialized User with no identity in DB.
2) define a new metod just to check if the user is an admin, for example:
# your unmodified current_user implementation
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def is_an_admin?
if current_user && current_user.admin?
end
So that you can use it in this way:
<% if is_an_admin? %>
<div>
<%= do stuff....%>
...It might be an extra method call, but it might also make your code more readable.
I know this is old, but if someone is googling the error as I did, there is actually no error in Rails Tutorial, but they forgot to highlight one thing they added.
Listing 9.54
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
Note that they added :destroy action here, not added before, which makes sure that the user is logged to perform destroy action and just then checks if he's an admin
before_action :admin_user, only: :destroy
Correction:
As of the time of this edit 12/14/2015, the rails tutorial now adds the :destroy action in Listing 9.53. If you miss that one as I did, you will get this error.
it looks like in your User model:
attr_accessible :email, :password, :password_confirmation#, :admin
admin is commented out, you need to delete the #.
First, I've searched intensely with Google and Yahoo and I've found several replies on topics like mine, but they all don't really cover what I need to know.
I've got several user models in my app, for now it's Customers, Designers, Retailers and it seems there are yet more to come. They all have different data stored in their tables and several areas on the site they're allowed to or not. So I figured to go the devise+CanCan way and to try my luck with polymorphic associations, so I got the following models setup:
class User < AR
belongs_to :loginable, :polymorphic => true
end
class Customer < AR
has_one :user, :as => :loginable
end
class Designer < AR
has_one :user, :as => :loginable
end
class Retailer < AR
has_one :user, :as => :loginable
end
For the registration I've got customized views for each different User type and my routes are setup like this:
devise_for :customers, :class_name => 'User'
devise_for :designers, :class_name => 'User'
devise_for :retailers, :class_name => 'User'
For now the registrations controller is left as standard (which is "devise/registrations"), but I figured, since I got different data to store in different models I'd have to customize this behaviour as well!?
But with this setup I got helpers like customer_signed_in? and designer_signed_in?, but what I'd really need is a general helper like user_signed_in? for the areas on the site that are accessible to all users, no matter which user type.
I'd also like a routes helper like new_user_session_path instead of the several new_*type*_session_path and so on. In fact all I need to be different is the registration process...
So I was wondering IF THIS IS THE WAY TO GO for this problem? Or is there a better/easier/less must-customize solution for this?
Okay, so I worked it through and came to the following solution.
I needed to costumize devise a little bit, but it's not that complicated.
The User model
# user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
belongs_to :rolable, :polymorphic => true
end
The Customer model
# customer.rb
class Customer < ActiveRecord::Base
has_one :user, :as => :rolable
end
The Designer model
# designer.rb
class Designer < ActiveRecord::Base
has_one :user, :as => :rolable
end
So the User model has a simple polymorphic association, defining if it's a Customer or a Designer.
The next thing I had to do was to generate the devise views with rails g devise:views to be part of my application. Since I only needed the registration to be customized I kept the app/views/devise/registrations folder only and removed the rest.
Then I customized the registrations view for new registrations, which can be found in app/views/devise/registrations/new.html.erb after you generated them.
<h2>Sign up</h2>
<%
# customized code begin
params[:user][:user_type] ||= 'customer'
if ["customer", "designer"].include? params[:user][:user_type].downcase
child_class_name = params[:user][:user_type].downcase.camelize
user_type = params[:user][:user_type].downcase
else
child_class_name = "Customer"
user_type = "customer"
end
resource.rolable = child_class_name.constantize.new if resource.rolable.nil?
# customized code end
%>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= my_devise_error_messages! # customized code %>
<div><%= f.label :email %><br />
<%= f.email_field :email %></div>
<div><%= f.label :password %><br />
<%= f.password_field :password %></div>
<div><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></div>
<% # customized code begin %>
<%= fields_for resource.rolable do |rf| %>
<% render :partial => "#{child_class_name.underscore}_fields", :locals => { :f => rf } %>
<% end %>
<%= hidden_field :user, :user_type, :value => user_type %>
<% # customized code end %>
<div><%= f.submit "Sign up" %></div>
<% end %>
<%= render :partial => "devise/shared/links" %>
For each User type I created a separate partial with the custom fields for that specific User type, i.e. Designer --> _designer_fields.html
<div><%= f.label :label_name %><br />
<%= f.text_field :label_name %></div>
Then I setup the routes for devise to use the custom controller on registrations
devise_for :users, :controllers => { :registrations => 'UserRegistrations' }
Then I generated a controller to handle the customized registration process, copied the original source code from the create method in the Devise::RegistrationsController and modified it to work my way (don't forget to move your view files to the appropriate folder, in my case app/views/user_registrations
class UserRegistrationsController < Devise::RegistrationsController
def create
build_resource
# customized code begin
# crate a new child instance depending on the given user type
child_class = params[:user][:user_type].camelize.constantize
resource.rolable = child_class.new(params[child_class.to_s.underscore.to_sym])
# first check if child instance is valid
# cause if so and the parent instance is valid as well
# it's all being saved at once
valid = resource.valid?
valid = resource.rolable.valid? && valid
# customized code end
if valid && resource.save # customized code
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => redirect_location(resource_name, resource)
else
set_flash_message :notice, :inactive_signed_up, :reason => inactive_reason(resource) if is_navigational_format?
expire_session_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords(resource)
respond_with_navigational(resource) { render_with_scope :new }
end
end
end
What this all basically does is that the controller determines which user type must be created according to the user_type parameter that's delivered to the controller's create method by the hidden field in the view which uses the parameter by a simple GET-param in the URL.
For example:
If you go to /users/sign_up?user[user_type]=designer you can create a Designer.
If you go to /users/sign_up?user[user_type]=customer you can create a Customer.
The my_devise_error_messages! method is a helper method which also handles validation errors in the associative model, based on the original devise_error_messages! method
module ApplicationHelper
def my_devise_error_messages!
return "" if resource.errors.empty? && resource.rolable.errors.empty?
messages = rolable_messages = ""
if !resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
end
if !resource.rolable.errors.empty?
rolable_messages = resource.rolable.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
end
messages = messages + rolable_messages
sentence = I18n.t("errors.messages.not_saved",
:count => resource.errors.count + resource.rolable.errors.count,
:resource => resource.class.model_name.human.downcase)
html = <<-HTML
<div id="error_explanation">
<h2>#{sentence}</h2>
<ul>#{messages}</ul>
</div>
HTML
html.html_safe
end
end
UPDATE:
To be able to support routes like /designer/sign_up and /customer/sign_up you can do the following in your routes file:
# routes.rb
match 'designer/sign_up' => 'user_registrations#new', :user => { :user_type => 'designer' }
match 'customer/sign_up' => 'user_registrations#new', :user => { :user_type => 'customer' }
Any parameter that's not used in the routes syntax internally gets passed to the params hash. So :user gets passed to the params hash.
So... that's it. With a little tweeking here and there I got it working in a quite general way, that's easily extensible with many other User models sharing a common User table.
Hope someone finds it useful.
I didn't manage to find any way of commenting for the accepted answer, so I'm just gonna write here.
There are a couple of things that don't work exactly as the accepted answer states, probably because it is out of date.
Anyway, some of the things that I had to work out myself:
For the UserRegistrationsController, render_with_scope doesn't exist any more, just use render :new
The first line in the create function, again in the UserRegistrationsController doesn't work as stated. Just try using
# Getting the user type that is send through a hidden field in the registration form.
user_type = params[:user][:user_type]
# Deleting the user_type from the params hash, won't work without this.
params[:user].delete(:user_type)
# Building the user, I assume.
build_resource
instead of simply build_resource. Some mass-assignment error was coming up when unchanged.
If you want to have all the user information in Devise's current_user method, make these modifications:
class ApplicationController < ActionController::Base
protect_from_forgery
# Overriding the Devise current_user method
alias_method :devise_current_user, :current_user
def current_user
# It will now return either a Company or a Customer, instead of the plain User.
super.rolable
end
end
I was following the above instructions and found out some gaps and that instructions were just out of date when I was implementing it.
So after struggling with it the whole day, let me share with you what worked for me - and hopefully it will save you few hours of sweat and tears
First of all, if you are not that familiar with RoR polymorphism, please go over this guide:
http://astockwell.com/blog/2014/03/polymorphic-associations-in-rails-4-devise/
After following it you will have devise and user users models installed and you will be able to start working.
After that please follow Vapire's great tutorial for generating the views with all the partails.
What I found most frustrating was that dut to using the latest version of Devise (3.5.1), RegistrationController refused to work.
Here is the code that will make it work again:
def create
meta_type = params[:user][:meta_type]
meta_type_params = params[:user][meta_type]
params[:user].delete(:meta_type)
params[:user].delete(meta_type)
build_resource(sign_up_params)
child_class = meta_type.camelize.constantize
child_class.new(params[child_class.to_s.underscore.to_sym])
resource.meta = child_class.new(meta_type_params)
# first check if child intance is valid
# cause if so and the parent instance is valid as well
# it's all being saved at once
valid = resource.valid?
valid = resource.meta.valid? && valid
# customized code end
if valid && resource.save # customized code
yield resource if block_given?
if resource.persisted?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
respond_with resource, location: after_sign_up_path_for(resource)
else
set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_flashing_format?
expire_data_after_sign_in!
respond_with resource, location: after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
set_minimum_password_length
respond_with resource
end
end
end
and also add these overrides so that the redirections will work fine:
protected
def after_sign_up_path_for(resource)
after_sign_in_path_for(resource)
end
def after_update_path_for(resource)
case resource
when :user, User
resource.meta? ? another_path : root_path
else
super
end
end
In order that devise flash messages will keep working you'll need to update config/locales/devise.en.yml instead of the overridden RegistraionsControlloer by UserRegistraionsControlloer all you'll need to do is add this new section:
user_registrations:
signed_up: 'Welcome! You have signed up successfully.'
Hope that will save you guys few hours.