Devise: => #<NoMethodError: undefined method `downcase!' for 1:Fixnum> - ruby-on-rails

This may or may not be a devise error, but I think it is.
In testing I tried assigning an integer as the email address. I am getting this error from the 'bowels' of active record save:
=> #<NoMethodError: undefined method `downcase!' for 1:Fixnum>
This is despite the fact that I have this validation on my User < ActiveRecord::Base.
validates :email, format: { with: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i, message: "Invalid email"}
I am guessing that somehow some devise validation or other hook is being called on the email address and blowing up. What can I try next?

Have a look at the Devise sourcecode:
The authenticable module adds before_validation :downcase_keys, which does the following:
def downcase_keys
self.class.case_insensitive_keys.each { |k| apply_to_attribute_or_variable(k, :downcase!) }
end
(see lib/devise/models/authenticatable.rb for reference)
It is used to downcase all case insensitive keys before applying any logic to the models. By default configuration provided by Devise, case_insensitive_keys contains the email field you are setting with a Fixnum.
# Keys that should be case-insensitive.
mattr_accessor :case_insensitive_keys
##case_insensitive_keys = [ :email ]
(see lib/devise.rb for reference)
This way, the validation is executed even before your own validation is checked. Perhaps an additional verification for the variable's type could be added to Devise, but by default you'll only assign a String provided by the login / etc. form to that field.
I hope i could clarify this a bit.

Related

Rails 4 before_save attribute changed encrypting password

So I wrote an app before that allowed for the standard way of encrypting a password using this and it worked fine:
before_save :create_hashed_password
Then:
def create_hashed_password
# validation code not shown
self.password = Digest::SHA1.hexdigest(password)
end
The problem is now in this app is that I have other user attributes I want to edit and every time I edit and save, I am hashing the already hashed password, thus making login impossible after updating.
I tested this in irb and it works:
irb(main):008:0> t.password = 'password'
=> "password"
irb(main):009:0> t.password_changed?
=> true
But when I use this line in the before filter:
before_save :create_hashed_password if password_changed?
It fails with the following error:
NoMethodError: undefined method `password_changed?' for User(no database connection):Class
(And before you ask, yes I do have a db connection, it's just with the User model because the before filter is there)
BTW I'm on Rails 4.
Try with:
before_save :create_hashed_password, if: :password_changed?
Short explanation: in your current syntax, the if part is not a param to the before_save method, this is why you need to add the coma, to send it as a parameter. Now it tries to call a class method: User.password_changed?, this doesn't make sense since you need to perform an instance method against a user object.
Try this:
before_save :create_hashed_password, if: Proc.new { &:password_changed? }
Hope this helps, happy coding

Handling devise registration errors

I've subclassed the devise RegistrationsController for creating new users and added some logic before calling the superclass's 'create' method. So, something like:
class RegistrationsController < Devise::RegistrationsController
def create
super end
I can tell if the superclass encountered an error by checking resource.errors.nil?. However, I want to distinguish between different errors. For instance, I want to do something different if the error is "Email has already been taken" versus some other error return.
I can parse the string, but that seems fragile to me. What if some future upgrade of ActiveRecord or Devise changes the string? What if the string get's localized in some way I don't expect?
Is anyone handling error processing in devise more gracefully than string parsing?
you can modify devise.en.yml for any default errors
Notice that the devise_error_messages helper is just running through the errors attached to whatever you have assigned as your resource object (whatever user model you ran the install generator on).
Now, instead of just printing out the error messages in the helper, you could access their keys in a controller method, as well:
# in RegistrationsController
def create
build_resource
unless resource.valid?
if resource.errors.has_key?(:my_error_key)
# do something
end
end
end
This is just an example, of course, but hopefully it illustrates the approach you might take.
With Rails validation errors the devil is in the #details.
A typical validation error on presence looks like this:
> #user.errors
#<ActiveModel::Errors:0x007fe7e8f01234 #base=#<User id: nil,
email: "someone#else.mail", created_at: nil, updated_at: nil>,
#messages={:password=>["can't be blank"], :email=>[]},
#details={:password=>[{:error=>:blank}]}>
As you see, the failed validation is accurately described in #details of the ActiveModel Error object.
There is even a short hand method in Rails that makes it easy to test for specific validation errors:
#user.errors.added? :password, :blank
If the password is left blank, this will return true.
More about added? in the Ruby on Rails API: http://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-added-3F

Rails custom validation message with helper method

I'm trying to give a custom validation message to a uniqueness validation. However, for the error message I need slightly complicated behavior, so I'm putting this logic in a private method (error_message_for_email_uniqueness).
Here's the code I'm using
validates_uniqueness_of :email, message: error_message_for_email_uniqueness
Here's the error I'm getting
/Users/dylandrop/.rvm/gems/ruby-1.9.2-p290/gems/attr_encrypted-1.2.0/lib/attr_encrypted.rb:229:in `method_missing': undefined local variable or method `error_message_for_email_uniqueness' for #<Class:0x00000103684198> (NameError)
I've tried using message: lambda { error_message_for_email_uniqueness }, which also doesn't work. Also, I've tried wrapping it in a Proc instead of a lambda, which doesn't get me anywhere.
How can I get around this?
Did you define error_message_for_email_uniqueness as a class method?
I did a quick test, and this works fine:
validates_uniqueness_of :email, message: Proc.new { error_message_for_email_uniqueness }
def self.error_message_for_email_uniqueness
# logic to generate message goes here
end
If you want to create messages dynamically based on model attributes or something, you should create custom validator, because message is expected to be string.

Display link in Rails form error message

On our sign-up form, we validates_uniqueness_of :email
When the a user is attempting to use our sign up form and they specify an existing email address, I'd like them to see an error message like this
This email address is already in use. If you're having trouble logging in, you can reset your password
Obviously, I'd like to use the named route for the link, but my User model does not have access to it. How can I accomplish this?
Side note: We will be offering translations for our app soon and all of these error messages will end up in YAML files. Can I somehow inject my new_password_url in a message in my YAML locale files? (e.g., config/locales/en.yml)
I know this is an old question, but for future users who want to insert a link into an error message, here are some guidelines that worked for me.
First, the I18n error messages are assumed html safe already, so you can go ahead and write a suitable error message. In this example, I'm changing an "email is taken" message.
# config/locales/en.yml
activerecord:
errors:
models:
user:
attributes:
email:
taken: 'has already been taken. If this is your email address, try logging in instead.'
Notice the interpolated variable %link.
Now all you need to is pass in a value for that variable in your validator, like so:
# app/models/user.rb
validates :email, :uniqueness => {:link => Rails.application.routes.url_helpers.login_path}
(By default, any options you pass in here will automatically be sent over to the I18n translator as variables, including some special pre-populated variables like %value, %model, etc.)
That's it! You now have a link in your error message.
This may not streamline well with the translations, but here's a suggestion:
In your user_controller#create action, wrap everything you already have with an if statement. Here's a rough example:
class UserController < ApplicationController
...
def create
if User.find(params[:email])
flash[:alert] = "This email address is in use. You can ".concat(generate_reset_password_link(params[:email])
render :action => 'new'
else
<your current code>
end
end
After this, you'll have to write a helper method generate_reset_password_link, but I think this mostly respects the MVC layout. The controller is meant to interface with the view and model. It is violating the DRY principle a little, since you're essentially bypassing validates_uniqueness_of :email, but you get some custom behavior. DRY doesn't seem to be 100% achievable to me if you want to make more complex apps, but perhaps you can refine this and prove me wrong ;)
You may have to massage this a little so that the render :action => 'new' will repopulate itself with the previously entered data (in case the user just mistyped his own email address and it actually isn't in the system).
If you decide to use this approach, I would throw a comment in both the controller and the model indicating that the email uniqueness is essentially checked in 2 places. In the event someone else has to look at this code, it'll help them to understand and maintain it.
You can place a tag of your own like ~[new_password_url] in your error messages. Then at the point of rendering your error messages gsub ur tag with the actual. if you want to do it generically you can get the path out using regexp and then eval it to get the url then gsub it back in. make you use the raw method if you are putting html into your text.
If you're using 2.3.x, replace your call to error_messages with your own helper, written in UsersHelper. It should accept the FormBuilder or an ActiveRecord object and adjust the error message as you see fit. You could make as many customizations as you like, or it could be as simple as a gsub:
def user_error_messages(f)
find_error = "This email address is already in use."
replacement = "This email address is already in use. #{link_to(...)} to reset your password"
f.error_messages.sub(find_error, replacement).html_safe
end
If you're using Rails3, make a helper method to simply process #user.errors.full_messages before they're emitted to the view.
Stumbled across this today:
http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html
If you need to access this auto-generated method from other places (such as a model), then you can do that by including ActionController::UrlFor in your class:
Step 1
Getting awareness of named routes to the model is the hard part; this gets me most of the way.
Now I can do something along the lines of
class User < ActiveRecord::Base
include Rails.application.routes.url_helpers
def reset_password_uri
new_user_password_path(self)
end
end
# User.find(1).reset_password_uri => "/users/password/new"
Step 2
So we have access to the named route, but now we need to inject it into the YAML message.
Here's what I just learned about YAML variables:
# en.yml
en:
welcome: "Hello, %{username}!"
# es.yml
es:
welcome: "¡Hola, %{username}!"
I can inject the username by including a hash with the t method
<div id="welcome">
<%= t :welcome, :username => #user.username %>
</div>
Step 3
Now we just need a way to add interpolation to the error message described in the original question. This is where I am currently stuck :(
After hours trying to figure this out for Rails 4 with devise, I realised you can just add the link directly into the validation:
# app/models/user.rb
validates :username, presence: true, uniqueness: {:message => "username has already been taken - <a href='/users'>search users</a>" }
where the link in this case is my users index. Hopefully this will help someone!

Rails: How to prevent 2 active-record attributes to be equal with validation?

I want to prevent users from signing up with a password = login for security reasons. I tried something like this:
validates_each :password do |record, attr, value|
if(value == self.login)
record.errors.add(attr)
end
end
But I always get the following error: undefined method login for self. It has something to do with the class hierachy I guess, but how do I access a higher level.
I'm a little stuck here, please help.
Try
if value == record.login
the record is passed into the block as the record local variable, and in this context is not self.

Resources