Rails PG::Error Presentation and Handling of Unique Constraint - devise

I am developing on Rails 4 with postgres and devise. I have the following User model:
# user model
class User < ActiveRecord::Base
extend FriendlyId
friendly_id :username, :use => :slugged
rolify
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable
validates :username, :presence => true, :uniqueness => {:case_sensitive => false}, :length => { :minimum => 3 },
:format => { :with => /\A[A-Z0-9a-z\w\b\ \-\_\'\!&##\.]+\z/i,
:message => "may contain only alphanumeric characters and common special characters." }
validates :email, :uniqueness => {:case_sensitive => false}, :presence => true,
:format => { :with => Devise.email_regexp, :message => "isn't valid"}
validates :password, length: { in: 6..128 }, on: :create
validates :password, length: { in: 6..128 }, on: :update, allow_blank: true
validates :slug, :presence => true
end
# in schema
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.string "username"
t.string "slug"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
add_index "users", ["slug"], name: "index_users_on_slug", unique: true
add_index "users", ["username"], name: "index_users_on_username", unique: true
The fields email and username are unique indices of the users table. My user registration view was created from devise. I create a test account, for instance, with username "test", email "test#example.com", password "123456". I sign out and try to sign up with the same information. I am expecting that the :uniqueness validations to trigger and be rendered on a list of errors alongside the signup form, but instead I get a full-page error with:
ERROR: duplicate key value violates unique constraint "index_users_on_email"
DETAIL: Key (email)=(test#example.com) already exists.
How can I allow this error to bubble up to Rails and be shown as friendly single-line error like "Email is already registered with an account" alongside the sign up form instead of being caught with a big Rails error page?

I've identified the problem. The plugin friendly_id turned out not so friendly. It breaks uniqueness validation and results in 500 internal server errors. I have removed it from my application. Consider using something like ActsAsUrl in the Stringex library. I have removed
extend FriendlyId
friendly_id :username_copy, :use => :slugged
and am now using
acts_as_url :username, :sync_url => true, :url_attribute => :slug
to generate friendly urls.

Related

Want to order with Count, Group by and Order DESC in Rails

This is my doubt, I have the following table:
create_table "followings", force: :cascade do |t|
t.integer "follower_id"
t.integer "followed_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["followed_id"], name: "index_followings_on_followed_id"
t.index ["follower_id", "followed_id"], name: "index_followings_on_follower_id_and_followed_id", unique: true
t.index ["follower_id"], name: "index_followings_on_follower_id"
end
And this is my user table:
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "photo"
t.string "coverimage"
t.string "fullname"
t.string "username"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
and I need to create a top 10 of the most followed users, so in this case, the most repetitive followed_id's, but I can not make an order by on a count just created column inside ActiveRecord. I am trying something like this:
#userst = Following.where(:group => "followed_id", :select => "device_id, COUNT(folloing_id)", sort: :dsc)
what I can be doing wrong?
this is my user controller (top is not finished yet as you can see):
class UsersController < ApplicationController
before_action :authenticate_user!
def index
#users = User.all
end
def show
#user = User.find(params[:id])
#posts = #user.posts.ordered_by_most_recent
end
def edit
#user = User.find(params[:id])
end
def following
#title = "Following"
#user = User.find(params[:id])
#users = #user.following.paginate(page: params[:page])
render 'show_follow'
end
def followers
#title = "Followers"
#user = User.find(params[:id])
#users = #user.followers.paginate(page: params[:page])
render 'show_follow'
end
def top
#userst = User.where()
end
end
Just to let you know this is the user model:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :authentication_keys => [:username]
validates :fullname, presence: true, length: { maximum: 20 }
validates :username, presence: true, length: { maximum: 20 }
validates_uniqueness_of :username
has_many :posts
has_many :active_followings, class_name: "Following",
foreign_key: "follower_id",
dependent: :destroy
has_many :passive_followings, class_name: "Following",
foreign_key: "followed_id",
dependent: :destroy
has_many :following, through: :active_followings, source: :followed
has_many :followers, through: :passive_followings, source: :follower
mount_uploader :photo, FileUploader
mount_uploader :coverimage, FileUploader
# Follows a user.
def follow(other_user)
following << other_user
end
# Unfollows a user.
def unfollow(other_user)
following.delete(other_user)
end
# Returns true if the current user is following the other user.
def following?(other_user)
following.include?(other_user)
end
end
Assuming you have a user model, and it contains a has_many relationship with the followings model, you can try with:
User.joins(:followings)
.order('COUNT(followings.followed_id) DESC')
.group('users.id')
.limit(10)

undefined method `zero?' for nil:NilClass on Devise

I'm installing Devise and I'm getting a undefined method `zero?' for nil:NilClass when trying to register a new user. Routing seams ok, as other pages such as sign in, sign up, lost password is showing up.
The error message:
NoMethodError in Devise::RegistrationsController#create
undefined method `zero?' for nil:NilClass
My Class User is as follows:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable #, :encryptable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :password_salt
# attr_accessible :title, :body
end
The migration is running OK as:
class DeviseCreateUsers < ActiveRecord::Migration
def change
create_table(:users) do |t|
## Database authenticatable
t.string :email, :null => false, :default => ""
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts
t.string :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
## Token authenticatable
t.string :authentication_token
t.string :password_salt
t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
add_index :users, :confirmation_token, :unique => true
add_index :users, :unlock_token, :unique => true
add_index :users, :authentication_token, :unique => true
end
end
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"MNwIeldQzcBbakbcLkbcIlNYiKQ72F3nJyqHOsDdRoM=",
"user"=>{"email"=>"email#example.com",
"password"=>"[FILTERED]",
"password_confirmation"=>"[FILTERED]"},
"commit"=>"Sign up",
"locale"=>"en"}
Devise Version:
2.1.2
Ruby version:
ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin12.3.0]
Rails version:
Rails 3.2.13

How could duplicate rows be created with the schema

In Rail, I have this schema (for a join table between car and user habtm)
create_table "cars_users", :id => false, :force => true do |t|
t.integer "user_id"
t.integer "car_id"
end
add_index "cars_users", ["car_id", "user_id"], :name => "index_cars_users_on_car_id_and_user_id"
add_index "cars_users", ["user_id", "car_id"], :name => "index_cars_users_on_user_id_and_car_id"
but due to some bug, I have duplicate rows in this table.
shouldn't the index handle that?
Try:
add_index :cars_users, [ :user_id, :car_id ], :unique => true, :name => 'by_user_and_car'
it will raise an exception but you can add your own validation
class User < ActiveRecord::Base
has_and_belongs_to_many :cars, :before_add => :validates_car
def validates_car(car)
errors.add(:car, "already created" ) if self.cars.include? car
end
end

Why is the user_id not automatically added upon creation of a comment?

I have a field that creates a comment (named pcomment). I am trying to get it to automatically add the user_id to the pcomment in the pcomment table like it adds the purchase_id automatically. I am not sure why the purchase_id is being recorded in the database but the user_id remains blank for each pcomment. Here is the form for the pcomment.
<%= form_for([purchase, purchase.pcomments.build], :html => { :id => "blah_form" }) do |f| %>
<div class="field">
<h4>What deal are you offering?</h4>
<%= f.text_field :body %>
</div>
<% end %>
It may be that I have to add some hidden_field, but I don't think so. I am using http://ruby.railstutorial.org/book/ruby-on-rails-tutorial#cha-user_microposts as resource and in that the microposts dont have any hidden_field. Instead, the user_id is indexed and it automatically is created upon the creation of a micropost (based on who is signed in at the time). This part is working for me too, adding to my rational that indexing user_id on the pcomments table is enough to automatically generate it. Here is my schema.rb file so that you can see the current state of my database.
ActiveRecord::Schema.define(:version => 20121011085147) do
create_table "pcomments", :force => true do |t|
t.string "body"
t.integer "purchase_id"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "pcomments", ["purchase_id"], :name => "index_pcomments_on_purchase_id"
add_index "pcomments", ["user_id"], :name => "index_pcomments_on_user_id"
create_table "purchases", :force => true do |t|
t.string "content"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "purchases", ["user_id", "created_at"], :name => "index_purchases_on_user_id_and_created_at"
create_table "sales", :force => true do |t|
t.string "content"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "sales", ["user_id", "created_at"], :name => "index_sales_on_user_id_and_created_at"
create_table "scomments", :force => true do |t|
t.string "body"
t.integer "sale_id"
t.integer "user_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "scomments", ["sale_id"], :name => "index_scomments_on_sale_id"
add_index "scomments", ["user_id"], :name => "index_scomments_on_user_id"
create_table "users", :force => true do |t|
t.string "name"
t.string "email"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "password_digest"
t.string "remember_token"
t.boolean "admin", :default => false
end
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
add_index "users", ["remember_token"], :name => "index_users_on_remember_token"
end
and the reason I know its not working is that I check in the database and the pcomment is successfully created with all columns filled in including purchase_id but the user_id is still blank. also, the user has_many pcomments and has_many purchases. The purchase has_many pcomments and belongs_to user. The pcomment belongs_to user and belong_to purchase.
also, here is the pcomments_controller.rb
class PcommentsController < ApplicationController
before_filter :signed_in_user
def create
#purchase = Purchase.find(params[:purchase_id])
#pcomment = #purchase.pcomments.build(params[:pcomment], :user_id => #purchase.user_id)
#pcomment.purchase = #purchase
if #pcomment.save
flash[:success] = "Offer submited!"
redirect_to :back
else
render 'shared/_pcomment_form'
end
end
def new
#pcomment=purchase.pcomments.new
end
end
def new
#pcomment=purchase.pcomments.new(:user_id => purchase.user_id)
end
end
purchase.pcomments.build builds empty Pcomment object just with purchase_id filled from purchase. To assign also user_id pass the hash with attribute:
purchase.pcomments.build(:user_id => purchase.user_id)

Can we use refinerycms for user authentication purpose

Am developing one website for which I have to do user login and registration form for the authentication purpose. My question is can we do this using only refinerycms and if it is possible please tell me how to do this. If it is not possible then please tell me which is the best approach. Am trying to implement user authentication using refinerycms. Am new to refinerycms.
Refinery uses devise so you can use that as well. You can create a model that might look something like this:
module Refinery
module Partners
class Partner < Refinery::Core::BaseModel
self.table_name = 'refinery_partners'
acts_as_indexed :fields => [:name]
validates :email, :presence => true, :uniqueness => true
#devise methods
devise :database_authenticatable, :recoverable, :rememberable, :trackable,:validatable, :authentication_keys => [:email]
end
end
end
and not to forget the migration:
class CreatePartnersPartners < ActiveRecord::Migration
def up
create_table :refinery_partners do |t|
t.string :email
t.string :name
## Database authenticatable
t.string :encrypted_password, :null => false, :default => ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, :default => 0
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
t.timestamps
end
end
def down
if defined?(::Refinery::UserPlugin)
::Refinery::UserPlugin.destroy_all({:name => "refinerycms-partners"})
end
if defined?(::Refinery::Page)
::Refinery::Page.delete_all({:link_url => "/partners/partners"})
end
drop_table :refinery_partners
end
end
This should create the basic model that you can use. To finish up, change the routes.rb in your extension to add devise routes:
devise_for :partners, :class_name => "Refinery::Partners::Partner",
:controllers => {:sessions => 'refinery/partners/sessions', :passwords => 'refinery/partners/passwords'}
and override refinery/partners/passwords_controller.rb
module Refinery
module Partners
class PasswordsController < Devise::PasswordsController
end
end
end
and refinery/partners/sessions_controller.rb
module Refinery
module Partners
class SessionsController < Devise::SessionsController
end
end
end
now you should have everything you need