How to set a virtual attribute in model with Rails? - ruby-on-rails

I have a model User with usual attributes email, email_confirmation, password, password_confirmation. I use "has_secure_password" so the real attributes in the database are email and password_digest. However I would like to limit password length without spaces to 6 characters.
Here is my model :
class User < ActiveRecord::Base
before_validation :auto_strip_confirmation
validates :email, presence: true,
length: { maximum: MAX_SIZE_DEFAULT_INPUT_TEXT },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false },
confirmation: true
validates :email_confirmation, presence: true
has_secure_password
validates :password, length: { minimum: MIN_SIZE_USER_PASSWORD,
maximum: MAX_SIZE_USER_PASSWORD }
private
def auto_strip_confirmation
self.password.strip!
self.password_confirmation.strip!
end
end
But I get this in console :
> user.password = user.password_confirmation = "a "
=> "a "
> user.valid?
User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER('user#example.com') LIMIT 1
=> true
Thanks for your help.

After reload, my code actually works in console : user.valid? => false (thanks surendran). But my initial problem was in my tests : I thought I could not set virtual attributes because of the error message "undefined method `strip!' for nil:NilClass". But I forgot I test if my user is valid when password is nil, nearly like this :
before { user.password = nil) }
it { should_not be_valid }
before_validation comes before this test so he tries to strip a nil object.

Related

Rails | Validate email address wth specific extension

Is there a way to validate email addresses such as 'myemail#mydomain.com' ?
So I should check if user has #mydomain.com extension on signup? How would regex code would look like?
This is what I am using currently, would like to add user.type = special if validation passes #mydomain.com if else user.type = normal
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
You can add model level validation on email attribute
validates :email, format: { with: /#mydomain\.com\z/i }
However the above regex doesn't put any restrictions on part of email before #
Edit:
You will have to add a before_create action which does check for #mydomain.com
before_create :set_user_type
def set_user_type
if /#mydomain\.com\z/i.match(email)
self.type = :special
else
self.type = :normal
end
end

How could patch method in Rails update only some attributes while the other attributes are blank?

I have these questions while reading the Ruby On Rails Tutorial in
here
The validation of User class is:
class User < ActiveRecord::Base
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, length: { minimum: 6 }, allow_blank: true
.
.
.
end
In test,while patching a updated user information to the route of the user like this:
def setup
#user = users(:michael)
end
.
.
.
test "successful edit" do
get edit_user_path(#user)
assert_template 'users/edit'
name = "Foo Bar"
email = "foo#bar.com"
patch user_path(#user), user: { name: name,
email: email,
password: "",
password_confirmation: "" }
assert_not flash.empty?
assert_redirected_to #user
#user.reload
assert_equal name, #user.name
email, #user.email
end
The test would pass and only the user's name and email would be updated and password won't change.
If the validation of the password doesn't include the "allow_blank:true",this test would fail.
So I don't understand that: When the test passed which means the password could be blank, why it wouldn't change the password to be blank? How could Rails know I just want to update some of the attributes?
has_secure_password adds a password= setter method method to your model which discards empty? input when setting the password.
irb(main):012:0> "".empty?
=> true
This prevents users from choosing a blank password. If you dont want to take my word for it you can easily test this:
test "does not change password to empty string" do
patch user_path(#user), user: { name: name,
email: email,
password: "",
password_confirmation: "" }
#user.reload
assert_false #user.authenticate("")
end
However what your validation does do is that if the user sets a password it must be over 6 characters:
test "does not allow a password less than 6 characters" do
patch user_path(#user), user: { name: name,
email: email,
password: "abc",
password_confirmation: "abc" }
assert assigns(:user).errors.key?(:password)
end
(PS. this is something that is better tested in a model test than a controller test)

Rails unit test failing

The following User test passes with no problem, the user is valid:
user_test.rb:
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com", callsign: "example",
password: "foobar", password_confirmation: "foobar")
end
test "user should be valid" do
assert #user.valid?
end
end
User model:
class User < ActiveRecord::Base
attr_accessor :remember_token, :activation_token, :reset_token
has_many :personas, dependent: :destroy
has_secure_password
before_save do
email.downcase!
callsign.downcase!
end
before_create :create_activation_digest
validates :name, presence: true,
length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(?:\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
VALID_CALLSIGN_REGEX = /\A[a-z\d\-.\_]+\z/i
validates :callsign, presence: true,
length: { maximum: 20 },
format: { with: VALID_CALLSIGN_REGEX },
uniqueness: { case_sensitive: false }
validates :password, length: { minimum: 6 }, allow_blank: true
def to_param
callsign
end
.
.
end
However, when I set up exactly the same user in the persona_test, the validation fails. (The persona validation fails too, each User has_many personas)
persona_test.rb:
require 'test_helper'
class PersonaTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com", callsign: "example",
password: "foobar", password_confirmation: "foobar")
#persona = #user.personas.build(name: "Bazman", callsign: "Baz")
end
test "user should be valid" do
assert #user.valid?
end
test "persona should be valid" do
assert #persona.valid?
end
end
Persona model:
class Persona < ActiveRecord::Base
belongs_to :user
before_save do
self.callsign.downcase!
set_persona_id
end
validates :name, presence: true,
length: { maximum: 50 }
VALID_CALLSIGN_REGEX = /\A[a-z\d\-.\_]+\z/i
validates :callsign, presence: true,
length: { maximum: 20 },
format: { with: VALID_CALLSIGN_REGEX },
uniqueness: { case_sensitive: false }
validates :user_id, presence: true
validates :persona_id, presence: true
def to_param
callsign
end
.
.
end
Failed test output:
FAIL["test_user_should_be_valid", PersonaTest, 0.754914]
test_user_should_be_valid#PersonaTest (0.75s)
Failed assertion, no message given.
test/models/persona_test.rb:18:in `block in <class:PersonaTest>'
FAIL["test_persona_should_be_valid", PersonaTest, 0.893247]
test_persona_should_be_valid#PersonaTest (0.89s)
Failed assertion, no message given.
test/models/persona_test.rb:22:in `block in <class:PersonaTest>'
I don't understand why the User validation in persona_test.rb is failing when the setup user is identical to the one in user_test.rb. Are you not allowed to test Users in a Personas test? If so, how do I successfully test personas? Each persona belongs_to a user, so I have to create a user in order to create a persona.
EDIT:
persona_test.rb:
require 'test_helper'
class PersonaTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com", callsign: "example",
password: "foobar", password_confirmation: "foobar")#, activated: true)
#persona = #user.personas.build(name: "Bazman", callsign: "Baz")
#persona.user = #user
#persona.persona_id = 1
end
test "user should be valid" do
assert #user.valid?, #user.errors.full_messages
end
test "persona should be valid" do
assert #persona.valid?, #persona.errors.full_messages
end
end
With the updated persona test above, I get the error message 'User can't be blank'. Why is
#persona.user = #user
not working?
The reason for the failed assertion is that some validations in Persona won't pass:
validates :user_id, presence: true
validates :persona_id, presence: true
The validations are run before saving them to the database. For new records, user_id and persona_id will still be nil.
Because Persona is invalid, the User will be invalid in the other test as well.
In your persona model you have:
validates :user_id, presence: true
validates :persona_id, presence: true
But it doesn't look like a user_id is being set. Try setting it with #persona.user = #user in your test.
Additionally, as a tool for debugging, you can print #persona.errors.full_messages in your test to see where exactly it is not validating.
E.g. assert #persona.valid?, #persona.errors.full_messages
Hope that helps.
EDIT: as per the comments below, the line should actually be #persona.user_id = #user.id. Another way you could achieve the same effect is to actually save the records to the database. So in your setup function, you would use create instead of build. This would, however, be slower.

Assigning to a class attribute from a method rails

Following the Michael Hartl rails tutorial. Struggling to get the remember token tests to pass, specifically, the test for non-blankness of the remember token.
The code for the User class is below
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
has_secure_password
before_save { |user| user.email = email.downcase }
before_save { :create_remember_token }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: {case_sensitive: false}
validates :password, presence: true, length: { minimum: 6 }
validates :password_confirmation, presence: true
private
def create_remember_token
self.remember_token = SecureRandom.urlsafe_base64
end
end
and the test I can't get to pass:
subject { #user }
...
describe "remember token" do
before { #user.save }
its (:remember_token) { should_not be_blank }
end
and the error message I get is:
.....................F
Failures:
1) User remember token remember_token
Failure/Error: its (:remember_token) { should_not be_blank }
expected blank? to return false, got true
# ./spec/models/user_spec.rb:120:in `block (3 levels) in <top (required)>'
Finished in 0.68878 seconds
22 examples, 1 failure
Failed examples:
rspec ./spec/models/user_spec.rb:120 # User remember token remember_token
I don't know if this is relevant but sublime_text isn't doing anything with the self keyword (ie its not highlighting it in a different colour).
I'm using Ruby 1.9.3
The issue is that you're writing before_save { :create_remember_token } when you should have before_save :create_remember_token. The { } is a block. Same as when you do
do
#This is some code
end
That is also a block of code.
That's why your first before_save works, because you're giving the block a piece of code to execute. In the second before_save you're just giving it the name of the method to execute which has the block of code.
Tl:dr:
Change
before_save { :create_remember_token }
to
before_save :create_remember_token
and you should be good to go.

Hide password_digest error message in JSON output

I'm building a simple JSON API using the rails-api gem.
models/user.rb:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email, :password, :password_confirmation
validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i }
validates :password, presence: { on: :create }, length: { minimum: 6 }
end
When I try to sign up without a password this is the JSON output I get:
{
"errors": {
"password_digest": [
"can't be blank"
],
"password": [
"can't be blank",
"is too short (minimum is 6 characters)"
]
}
}
Is it possible to hide the error message for password_digest? I'm returning the #user object with respond_with in the controller.
I've tried the following but no luck (it just duplicates the error "can't be blank"):
validates :password_digest, presence: false
#freemanoid: I tried your code, and it didn't work. But it gave me some hints. Thanks! This is what worked for me:
models/user.rb
after_validation { self.errors.messages.delete(:password_digest) }
You can manually delete this message in json handler in User model. Smth like:
class User < ActiveRecord::Base
def as_json(options = {})
self.errors.messages.delete('password_digest')
super(options)
end
end

Resources