Created
June 3, 2014 01:04
-
-
Save jrep/13b2b2dabb0e3fefa30e to your computer and use it in GitHub Desktop.
#update_attributes raises exception on ActiveRecord::RecordNotUnique
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Rails::version => "4.0.2" | |
ruby --version => ruby 2.1.0p0 (2013-12-25 revision 44422) [x86_64-darwin12.0] | |
mysql2 (0.3.16) | |
MySQL 5.5.35-0ubuntu0.12.04.2 | |
Ubuntu 12.04.4 LTS | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
An ActiveRecord::RecordNotUnique occurred in users#update: | |
Mysql2::Error: Duplicate entry '[email protected]' for key 'index_users_on_email': UPDATE `users` SET `email` = '[email protected]', `updated_at` = '2014-06-03 00:05:14' WHERE `users`.`id` = 6 | |
app/controllers/users_controller.rb:84:in `block in update' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ActiveRecord::Schema.define(version: 20140513172534) do | |
create_table "apis", force: true do |t| | |
t.string "provider" | |
t.string "endpoint" | |
t.string "name" | |
t.integer "owner_id" | |
t.string "status" | |
t.datetime "created_at" | |
t.datetime "updated_at" | |
t.string "apiary_dev_domain" | |
t.string "resource_root", default: "/v1", null: false | |
t.text "description" | |
end | |
create_table "users", force: true do |t| | |
t.string "login" | |
t.string "first_name" | |
t.string "last_name" | |
t.string "email" | |
t.datetime "created_at" | |
t.datetime "updated_at" | |
t.string "password_digest", default: "$2a$10$t6RvH7hLVrrcRJQk9d6ASe1sVBZhdyy/5TBQxZxofartejBSL0QyK" | |
t.string "remember_token", default: "lZzCzdaYXdqsTmfUtNXidQ" | |
t.boolean "admin", default: false | |
t.string "password_reset_token" | |
t.datetime "password_reset_expires_at" | |
t.boolean "email_confirmed" | |
t.string "email_confirmation_token" | |
t.datetime "email_confirmation_expires" | |
t.datetime "logged_in_at" | |
t.datetime "password_reset_at" | |
t.integer "times_logged_in", default: 0, null: false | |
t.integer "pages_visited", default: 0, null: false | |
t.datetime "last_seen" | |
end | |
add_index "users", ["email"], name: "index_users_on_email", unique: true | |
add_index "users", ["email_confirmation_token"], name: "index_users_on_email_confirmation_token" | |
add_index "users", ["login"], name: "index_users_on_login", unique: true | |
add_index "users", ["remember_token"], name: "index_users_on_remember_token" | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class User < ActiveRecord::Base | |
include ActionView::Helpers::DateHelper | |
validates :login, presence: true, uniqueness: true, length: { maximum: 80 } | |
validates :first_name, presence: true | |
validates :last_name, presence: true | |
VALID_EMAIL_REGEX = Rails.env.production? ? /\A[\w+\-.]+@akamai\.com\z/i : /\A[\w+\-.]+@(akamai|mailinator)\.com\z/i | |
validates :email, presence: true, uniqueness: true, format: { | |
with: VALID_EMAIL_REGEX, | |
message: Rails.env.production? ? "must be an Akamai email address." : "must be an Akamai or Mailinator email address." | |
} | |
has_secure_password | |
validates :password, length: { minimum: 6 }, :if => :password_required? | |
has_many :apis, foreign_key: 'owner_id' | |
before_create :create_remember_token | |
before_create :create_password_reset_token | |
before_save { self.login = login.downcase; self.email = email.downcase } | |
before_destroy :deny_owners | |
def generate_token(column) | |
begin | |
self[column] = SecureRandom.urlsafe_base64 | |
end while User.exists?(column => self[column]) | |
end | |
def User.new_remember_token | |
SecureRandom.urlsafe_base64 | |
end | |
def User.encrypt(token) | |
Digest::SHA1.hexdigest(token.to_s) | |
end | |
def send_password_reset(expiration = 2.hours.from_now) | |
generate_token(:password_reset_token) | |
self.password_reset_expires_at = expiration | |
save! | |
UserMailer.password_reset(self).deliver | |
end | |
def send_email_confirmation(expiration = 2.hours.from_now) | |
generate_token(:email_confirmation_token) | |
self.email_confirmation_expires = expiration | |
self.email_confirmed = false | |
save! | |
UserMailer.email_confirm(self).deliver | |
end | |
def count_pages_visited | |
User.increment_counter(:pages_visited, self) | |
end | |
def did_ago_in_words(timestamp) | |
send(timestamp) ? | |
"#{time_ago_in_words(send(timestamp), include_seconds:true)} ago" : | |
"(never)" | |
end | |
def long_name | |
@long_name ||= "#{ first_name } #{ last_name }" | |
end | |
def to_s | |
@to_s ||= "#{ long_name } (#{ login })" | |
end | |
private | |
def create_remember_token | |
self.remember_token = User.encrypt(User.new_remember_token) | |
end | |
def create_password_reset_token | |
self.password_reset_token = SecureRandom.urlsafe_base64 | |
end | |
def deny_owners | |
raise ArgumentError("Please reassign this user's APIs before deleting them.") if self.apis.any? | |
end | |
def password_required? | |
!persisted? || !password.nil? || !password_confirmation.nil? | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class UsersController < ApplicationController | |
helper_method :sort_column, :sort_direction | |
before_action :signed_in_user, except: [:new, :create] | |
before_action :count_pages_visited | |
before_action :record_last_seen | |
before_action :correct_user, only: [:edit, :update] | |
# GET /users | |
# GET /users.json | |
def index | |
# PERF: It would be more scalable to maintain down-cased sort columns for thi | |
@users = User.order("lower(#{sort_column}) #{sort_direction}") | |
end | |
# GET /users/1 | |
# GET /users/1.json | |
def show | |
@user = User.find(params[:id]) | |
end | |
# GET /users/new | |
def new | |
@user = User.new | |
end | |
# GET /users/1/edit | |
def edit | |
end | |
# POST /users | |
# POST /users.json | |
def create | |
@user = User.new(user_params) | |
respond_to do |format| | |
if @user.save | |
sign_in @user | |
format.html { redirect_to @user, notice: "Welcome to the {OPEN} API Registry" } | |
format.json { render action: 'show', status: :created, location: @user } | |
else | |
format.html { render action: 'new', notice: @user.errors } | |
format.json { render json: @user.errors, status: :unprocessable_entity } | |
end | |
end | |
end | |
# PATCH/PUT /users/1 | |
# PATCH/PUT /users/1.json | |
def update | |
# @user is the one being changed, by application of at_user_params | |
# current_user is the one doing the change | |
# they might be the same, but then again ... | |
at_user_params = user_params | |
# normalize inputs (admin sees abbreviated form, and has extra rights and restrictions) | |
if current_user.admin? | |
# would there still be at least one admin? | |
if @user.admin? && at_user_params[:admin] == '0' && User.where(admin: true).count < 2 | |
@user.errors.add :admin, 'must always have at least one.' | |
render 'edit' | |
return | |
end | |
else | |
# gave right password to update this user? | |
unless @user.authenticate(at_user_params[:password]) | |
@user.errors.add :password, :invalid | |
render 'edit' | |
return | |
end | |
# User setting new pw | |
if user_params.try(:[], :new_password).try(:present?) | |
at_user_params[:password] = user_params[:new_password] | |
at_user_params[:password_confirmation] = user_params[:confirmation] | |
else | |
at_user_params[:password_confirmation] = user_params[:password] | |
end | |
[:new_password, :confirmation].map { |i| at_user_params.delete(i) } | |
end | |
respond_to do |format| | |
if @user.update_attributes(at_user_params) | |
if at_user_params.include? :email | |
@user.send_email_confirmation ((@user == current_user) ? 2 : 8).hours.from_now | |
end | |
format.html { redirect_to @user, notice: 'Profile updated.' } | |
format.json { head :no_content } | |
else | |
format.html { render action:'edit' } | |
format.json { render json: @user.errors, status: :unprocessable_entity } | |
end | |
end | |
end | |
# GET /users/1/reset_password | |
# GET /users/1/reset_password.json | |
def reset_password | |
@user = User.find(params[:id]) | |
if current_user.admin? || @user == current_user | |
@user.send_password_reset ((@user == current_user) ? 2 : 8).hours.from_now | |
respond_to do |format| | |
format.html { redirect_to user_path(@user), notice: 'Email sent with password reset instructions.' } | |
format.json { head :no_content } | |
end | |
else | |
store_location | |
redirect_to users_path, notice: "Permission denied." | |
end | |
end | |
# GET /users/1/confirm_email | |
# GET /users/1/confirm_email.json | |
def confirm_email | |
@user = User.find(params[:id]) | |
if current_user.admin? || current_user == @user | |
@user.send_email_confirm ((@user == current_user) ? 2 : 8).hours.from_now | |
respond_to do |format| | |
format.html { redirect_to user_path(@user), notice: 'Email confirmation sent.' } | |
format.json { head :no_content } | |
end | |
else | |
store_location | |
redirect_to users_path, notice: "Permission denied." | |
end | |
end | |
# DELETE /users/1 | |
# DELETE /users/1.json | |
def destroy | |
User.find(params[:id]).destroy | |
flash[:success] = "User deleted." | |
respond_to do |format| | |
format.html { redirect_to users_path } | |
format.json { head :no_content } | |
end | |
end | |
private | |
# Never trust parameters from the scary internet, only allow the white list through. | |
USER_CREATE_PARAMS = [:login, :first_name, :last_name, :email, :password, :password_confirmation] | |
USER_PARAMS = USER_CREATE_PARAMS + [:new_password, :confirmation, :reset_password, :admin] | |
def user_params | |
params.require(:user).permit(USER_PARAMS) | |
end | |
def user_create_params | |
user_params.permit(USER_CREATE_PARAMS) | |
end | |
# Constrain profile edits to the user only | |
def correct_user | |
@user = User.find(params[:id]) | |
unless current_user?(@user) || current_user.admin? | |
store_location | |
redirect_to users_path, notice: "Permission denied." | |
end | |
end | |
def sort_column | |
User.column_names.include?(params[:sort]) ? params[:sort] : "last_name" | |
end | |
def sort_direction | |
%w[asc desc].include?(params[:direction]) ? params[:direction] : "asc" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment