NAthan Davis
NAthan Davis

Reputation: 15

rails console rollback on save

I am a new to rails. I am working with rails 4.2 and I broke something in my user.rb model or controller so that I can't use save or update in console or in in the web browser(for some varibles in user, I can change the name, email, and password in the browser). I found a similar problem here but I couldn't get there advice to work.

Here is my code User db table

create_table "users", force: :cascade do |t|
  t.string   "name"
  t.string   "email"
  t.datetime "created_at",                          null: false
  t.datetime "updated_at",                          null: false
  t.string   "avatar_file_name"
  t.string   "avatar_content_type"
  t.integer  "avatar_file_size"
  t.datetime "avatar_updated_at"
  t.string   "password_hash"
  t.string   "password_salt"
  t.boolean  "admin",               default: false
end

User.rb

class User < ActiveRecord::Base
    has_many :recipes
    has_many :comments

    #attr_accessible :email, :password, :password_confirmation
    attr_accessor :password

    before_save :encrypt_password

    validates_confirmation_of :password, :on => :create
    #validates :name, presence: true, uniqueness: true
    #validates :email, presence: true, uniqueness: true
    validates :password, presence: true, length: { in: 6..20 }


      has_attached_file :avatar, :styles => {
        :medium => "300x300>",
        :thumb => "100x100>"
      },
      :default_url => "/images/:style/missing.png",
      :bucket =>'davisrecipebook',
      :storage => :s3,
      :s3_credentials => "#{Rails.root}/config/s3.yml"
      #:s3_credentials => Proc.new{|a| a.instance.s3_credentials }
      #:storage => :dropbox,
      #:dropbox_credentials => Rails.root.join("config/dropbox.yml")

    #def s3_credentials
    #  {:bucket =>'davisrecipebook', :access_key_id => '', :secret_access_key => ''}
    #end


    validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
    validates_attachment_size :avatar, :less_than => 10.megabytes

    def self.authenticate(email, password)
        user = find_by_email(email)
        if user && user.password_hash == BCrypt::Engine.hash_secret(password, user.password_salt)
            user
        else
            nil
        end
    end

    def encrypt_password
        if password.present?
            self.password_salt = BCrypt::Engine.generate_salt
            self.password_hash = BCrypt::Engine.hash_secret(password, password_salt)
        end
    end
end

console out

2.1.5 :073 > User.last
  User Load (0.5ms)  SELECT  "users".* FROM "users"  ORDER BY "users"."id" DESC LIMIT 1
 => #<User id: 7, name: "hate", email: "death", created_at: "2014-12-28 07:32:22", updated_at: "2014-12-28 08:24:01", avatar_file_name: nil, avatar_content_type: nil, avatar_file_size: nil, avatar_updated_at: nil, password_hash: "$2a$10$9rEtvSoO0DOzm5u/Vop0O.5matAM8gTI0t6QZ470UyF...", password_salt: "$2a$10$9rEtvSoO0DOzm5u/Vop0O.", admin: false> 
2.1.5 :074 > s = _
 => #<User id: 7, name: "hate", email: "death", created_at: "2014-12-28 07:32:22", updated_at: "2014-12-28 08:24:01", avatar_file_name: nil, avatar_content_type: nil, avatar_file_size: nil, avatar_updated_at: nil, password_hash: "$2a$10$9rEtvSoO0DOzm5u/Vop0O.5matAM8gTI0t6QZ470UyF...", password_salt: "$2a$10$9rEtvSoO0DOzm5u/Vop0O.", admin: false> 
2.1.5 :075 > s.admin = true
 => true 
2.1.5 :076 > s.save
   (0.2ms)  BEGIN
  User Exists (0.6ms)  SELECT  1 AS one FROM "users" WHERE ("users"."name" = 'hate' AND "users"."id" != 7) LIMIT 1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users" WHERE ("users"."email" = 'death' AND "users"."id" != 7) LIMIT 1
   (0.2ms)  ROLLBACK
 => false 
2.1.5 :077 > s.errors

@messages={:password=>["can't be blank", "can't be blank", "is too short (minimum is 6 characters)"]}>

Upvotes: 1

Views: 2667

Answers (2)

Mohammad AbuShady
Mohammad AbuShady

Reputation: 42879

The thing is, the record doesn't contain a field called password, it has password_hash and password_salt, any time you try to save rails looks for the password field but can't find it, i'd say create a condition that tell rails when to look for a password, for example

validates :password, if: :password_required?
def password_required?
  user.changes.include? :email # just an example
end

This will prevent asking for passowrd every time the user changes something minor in his account, and it will ask for the password if the user changes his email, you could add more options of course.

If you want to force the saving of the user in the time being, you could tell rails to skip validations

user.save(validate: false)

Also as a last note, it's better and safer to use a well developed and mature authentication gem like devise, it has all the features you'd need in any authentication.

Upvotes: 3

AJcodez
AJcodez

Reputation: 34216

Your validations are failing. You have a validation for password, but there isn't a password field in the database (for security reasons, instead you save hash & salt).

To fix this, add the :on parameter to scope the validation to only run when users are created.

validates :password, on: :create, ...

From the rails docs

:on - Specifies the contexts where this validation is active. Runs in all validation contexts by default (nil). You can pass a symbol or an array of symbols.

Upvotes: 1

Related Questions