Skip to content

Instantly share code, notes, and snippets.

@jrep
Created June 3, 2014 01:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jrep/13b2b2dabb0e3fefa30e to your computer and use it in GitHub Desktop.
Save jrep/13b2b2dabb0e3fefa30e to your computer and use it in GitHub Desktop.
#update_attributes raises exception on ActiveRecord::RecordNotUnique
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
An ActiveRecord::RecordNotUnique occurred in users#update:
Mysql2::Error: Duplicate entry 'jack.repenning@akamai.com' for key 'index_users_on_email': UPDATE `users` SET `email` = 'jack.repenning@akamai.com', `updated_at` = '2014-06-03 00:05:14' WHERE `users`.`id` = 6
app/controllers/users_controller.rb:84:in `block in update'
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
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
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