I've got an app where users submit weeks which can be approved or denied, and in my weeks controller I have the following lines meant to iterate over the selected weeks, find their corresponding users and send each user an email:
elsif params[:commit] == "Reject selected weeks"
user_week = Week.where(id: params[:weeks_ids])
user_week.update_all(approved?: false)
# fetch the set of user_emails by converting the user_weeks to user_ids
users = User.find(user_week.pluck(:user_id))
users.each do |user|
#iterate over the users and send each one an email
UserMailer.send_rejection(user).deliver
end
flash[:info] = "Selected weeks were Rejected."
end
redirect_to weeks_path
When I attempt to reject a week, I receive the following error message:
undefined method `send_rejection' for UserMailer:Class
I'm adding on to pre-existing code and have little knowledge of MVC, so the only issues I can think of would be with placing the mailer method in the wrong file or sending an incorrect type of arg to the mailer method.
Here is "send_rejection", the mailer contained in my user model.
def send_rejection(user)
UserMailer.reject_timesheet(user).deliver_now
end
The corresponding method in my user_mailer.rb file:
def reject_timesheet(user)
#greeting = "Hi"
mail to: user.email, subject: "Rejected Timesheet"
end
New to rails and not sure where I'm going wrong.
This is not a problem of MVC, one question I'd probably ask is why are you not calling the reject_timesheet directly instead of send_rejection.
You're getting the error because as you said the method is defined in the user model, so in order to call the method, you'd need to do:
user.send_rejection
In which case I doubt you'd be needing to pass a user argument to the send_rejection, as you could just do:
class User
def send_rejection
UserMailer.reject_timesheet(self).deliver_now
end
end
then in your controller:
...
users.each do |user|
#iterate over the users and send each one an email
user.send_rejection
end
...
I believe you could also clean up your codebase a bit and possibly refactor some logic, but basically this approach should resolve your errors.
Let me know if that helps
I have a method adjusts an auto-generated email for a user as part of a before_validation callback:
def update_device_email
binding.pry
self.email = username.gsub(/\s/, '_') + FAKE_EMAIL_SUFFIX
self.email_confirmation = email
end
For some reason though when I hit the binding.pry call, it shows me a downcased version of the email, which then returns to normal after I call gsub:
Does anyone know why this may be happening?
You should be checking what the username returns prior to the changes being made to self.email
I'm having a very weird issue with devise when trying to edit a user's password - here is a simplified version of what I'm doing at the moment:
def update
user = User.find(params[:id])
password = params.delete(:password)
password_confirmation = params.delete(:password_confirmation)
if password.present? and password != "" and password == password_confirmation
user.password = password
end
user.update(params)
user.save
render json: user
end
What's really weird is that if I set password to an arbitrary string, by putting
password = "testpassword"
above
user.password = password
it works properly, the password is set to "testpassword", and i can log in using it just fine. But if I try and use the param like in the code above, I cannot log in using the password set in params[:password]. I have tried forcing the encoding of the string, and to use user.update_without_password, but to no avail.
Would anybody have any idea about this? it's driving me bonkers!
here is a dump of the params hash:
{"username"=>"testytest",
"first_name"=>"Test",
"last_name"=>"testy",
"password"=>"password",
"password_confirmation"=>"password"}
To be a bit more precise, trying to login once this has been fired results in me not being able to login using the old password, or the new one. if there was a way to see which password gets saved in that function, I should be able to debug it!
Well, after 3 days of struggle I finally came up with a solution, however it's pretty ugly: what I did was this (I've added a method to the user model to simplify):
def update_password(new_password)
password_forced = ""
new_password.each_byte do |byte|
password_forced << byte.chr
end
self.password = password_forced
self.save
end
Again, inspecting the string coming from the params didn't point to anything that might be different from another ruby string... but anyway, this works!
I am building a little application in Rails and what I am trying to do now is authenticate a user.
So I got this method in the controller class:
def login
if #user = User.authenticate(params[:txt_login], params[:txt_password])
session[:current_user_id] = #user.id
redirect_to root_url
end
end
Here is the definition of authenticate method (inside the User model class):
def self.authenticate(username, password)
#user = User.where(["username = ? AND password = ?", username, password])
return #user
end
The problem is that I get an error message saying:
undefined method `id' for #<ActiveRecord::Relation:0x92dff10>
I confirm that the user I was trying to log in really exists in the database (besides it tries to get the id of a user and this instruction is wrapped inside an if in case 0 users are returned from the authenticate method).
Why am I obtaining this error message? Knowing that when I change the User.where by User.find it works fine!
Thank you!
User.where("some_conditions") will return an array of User objects ( in simple terms ) , A User.find can return an array or a single object.( I am not sure because i don't see how you are using it )
As far what you see is ActiveRecord::Relation, this is what is returned when we call a find or a where or a order method on Rails 3 Models.
Also, You are storing password as a plain string which is a bad idea, you should use some available rails authentication plugins like Devise or Authlogic.
What's the best way to enable users to log in with their email address OR their username? I am using warden + devise for authentication. I think it probably won't be too hard to do it but i guess i need some advice here on where to put all the stuff that is needed. Perhaps devise already provides this feature? Like in the config/initializers/devise.rb you would write:
config.authentication_keys = [ :email, :username ]
To require both username AND email for signing in. But i really want to have only one field for both username and email and require only one of them. I'll just visualize that with some ASCII art, it should look something like this in the view:
Username or Email:
[____________________]
Password:
[____________________]
[Sign In]
I have found a solution for the problem. I'm not quite satisfied with it (I'd rather have a way to specify this in the initializer), but it works for now. In the user model I added the following method:
def self.find_for_database_authentication(conditions={})
find_by(username: conditions[:email]) || find_by(email: conditions[:email])
end
As #sguha and #Chetan have pointed out, another great resource is available on the official devise wiki.
From their Wiki — How To: Allow users to sign in using their username or email address.
def self.find_for_authentication(conditions)
conditions = ["username = ? or email = ?", conditions[authentication_keys.first], conditions[authentication_keys.first]]
# raise StandardError, conditions.inspect
super
end
Use their example!
Make sure you already added username field and add username to attr_accessible.
Create a login virtual attribute in Users
1) Add login as an attr_accessor
# Virtual attribute for authenticating by either username or email
# This is in addition to a real persisted field like 'username'
attr_accessor :login
2) Add login to attr_accessible
attr_accessible :login
Tell Devise to use :login in the authentication_keys
Modify config/initializers/devise.rb to have:
config.authentication_keys = [ :login ]
Overwrite Devise’s find_for_database_authentication method in Users
# Overrides the devise method find_for_authentication
# Allow users to Sign In using their username or email address
def self.find_for_authentication(conditions)
login = conditions.delete(:login)
where(conditions).where(["username = :value OR email = :value", { :value => login }]).first
end
Update your views
Make sure you have the Devise views in your project so that you can customize them
remove <%= f.label :email %>
remove <%= f.email_field :email %>
add <%= f.label :login %>
add <%= f.text_field :login %>
https://gist.github.com/867932 : One solution for everything. Sign in, forgot password, confirmation, unlock instructions.
Platforma Tec (devise author) has posted a solution to their github wiki which uses an underlying Warden authentication strategy rather than plugging into the Controller:
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-sign-in-using-their-username-or-email-address
(An earlier answer had a broken link, which I believe was intended to link to this resource.)
If you are using MongoDB (with MongoId), you need to query differently:
def self.find_for_database_authentication(conditions={})
self.any_of({name: conditions[:email]},{email: conditions[:email]}).limit(1).first
end
just so it will be somewhere online.
With squeel gem you can do:
def self.find_for_authentication(conditions={})
self.where{(email == conditions[:email]) | (username == conditions[:email])}.first
end
I wrote like this and it works out. Don't know if it's "ugly fix", but if I'll come up with a a better solution I'll let you know...
def self.authenticate(email, password)
user = find_by_email(email) ||
username = find_by_username(email)
if user && user.password_hash = BCrypt::Engine.hash_secret(password, user.password_salt)
user
else
nil
end
end
I use a quick hack for this, to avoid changing any devise specific code and use it for my specific scenario (I particularly use it for an API where mobile apps can create users on the server).
I have added a before_filter to all the devise controllers where if username is being passed, I generate an email from the username ("#{params[:user][:username]}#mycustomdomain.com") and save the user. For all other calls as well, I generate the email based on same logic. My before_filter looks like this:
def generate_email_for_username
return if(!params[:user][:email].blank? || params[:user][:username].blank?)
params[:user][:email] = "#{params[:user][:username]}#mycustomdomain.com"
end
I am also saving username in the users table, so I know that users with email ending in #mycustomdomain.com were created using username.
Here's a Rails solution which refactors #padde's answer. It uses ActiveRecord's find_by to simplify the calls, ensures there's only one call based on the regex, and also supports numeric IDs if you want to allow that (useful for scripts/APIs). The regex for email is as simple as it needs to be in this context; just checking for the presence of an # as I assume your username validtor doesn't allow # characters.
def self.find_for_database_authentication(conditions={})
email = conditions[:email]
if email =~ /#/
self.find_by_email(email)
elsif email.to_s =~ /\A[0-9]+\z/
self.find(Integer(email))
else
self.find_by_username(email])
end
end
Like the wiki and #aku's answer, I'd also recommend making a new :login parameter using attr_accessible and authentication_keys instead of using :email here. (I kept it as :email in the example to show the quick fix.)