Search code examples
rubyredissinatraohm

Overriding attributes of Ohm models


I'm experiencing a very weird problem with Ohm that I'm not able to track and solve. Ohm version is 2.0.1.

This is my code:

class User < Ohm::Model
  attribute :username
  attribute :password

  index :username

  def password= string
    @attributes[:password] = BCrypt::Password.create(string) # Tried self.password = BCrypt::whatever too
  end
end

[15] pry(main)> User.find(username: 'test').first.password
=> "$2a$10$j1.s4hmuyCm8RffaEvB8IejaYOiZXWXId1Ccf8S0K3uXduxmMzyUq"
[16] pry(main)> User.find(username: 'test').first.password
=> "$2a$10$/0UzWtVsF.xczf4.UUqrP.PqYHxKs8fkIWKHlVVQVUNPFubzmuCwO"
[17] pry(main)> User.find(username: 'test').first.password
=> "$2a$10$ajlc3BYMOFXYDmy1a112ieXhMm39KoR1wPdPMp4WwEnxb2E35ypvC"
[18] pry(main)> User.find(username: 'test').first.password
=> "$2a$10$TlW87Gpd4RKpPutWzkePqeQiGri2ah.txDda4o6Lki7Sk1vayY9Fm"

Basically I'm able to set the password and encrypt it using BCrypt, but for some reasons every time I call the attribute the password is different. I have no idea what's happening here, can someone help me?


Solution

  • When Ohm loads the attributes from the database, it doesn't assign it to @attributes directly. Instead, it uses the accessor to allow for any kinds of type casting. In your case, what's happening is that each time Ohm loads the password field from Redis, it processes it again with BCrypt. You can check the code in question.

    What I usually do is a bit different: I define an attribute crypted_password in Ohm, and then in the model I define a method password= that takes a string and produces the crypted version, which is then stored in the crypted_password attribute. I use a gem called Shield, here's the relevant code.

    You can do something similar to what Shield does, and use BCrypt instead. You have to define an attribute crypted_password, then a method called password= that is a bit different from what you currently have:

    class User < Ohm::Model
      attribute :username
      attribute :crypted_password
    
      index :username
    
      def password= string
        self.crypted_password = BCrypt::Password.create(string)
      end
    end
    

    That should work, and you will have to use crypted_password for authenticating users.