Undefined method for UserMailer:Class - ruby-on-rails

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

Related

Rails Controller runs update function anyway even though the form submit returns devise error message and nothing updated

Environment:
rails 4.2.6
devise 4.1.1
I am using a rails app, and there is a form to update user's profile. By default, devise asks the user to input user's password to update the user's data. I have put the <%=devise_error_messages! %> in the form, of course there is a update function in the controller, which looks like
def update
super
#email = resource.email
#event = resource.event
#name = resource.name
NoticeMailer.notice_confirm(#email, #name, #event).deliver_later
end
Here comes the problem. When I am editing the user's profile data, by default, devise asks the user to input the password to update the data and save it to the database. If I input the wrong password or leave the password field blank, and press the submi button(form.submit), there will be an error message, and I am still in the form. However even though there is an error message in the form, the update function in the controller still runs anyway. I think the logic is that the update function should not run if the update is failed.
Try01:
Try to input the data without password. I use the method
protected
def resource_update(params, resource)
resouce.update_without_password(params, resource)
end
in the controller,but it threw error message.
Try02
I am thinking using ajax to catch the submit click action and pass the password field to backend to check password. however i don't know how to implement this.
Try03
I tried to put a after_update filter function to do the mail sending function. However the result is the same, the mail function is sending no matter what.
Any suggestion?
I would expect the resource to be not valid? if the update failed. Therefore I would try:
def update
super
NoticeMailer.notice_confirm(
resource.email, resource.name, resource.event
).deliver_later if resource.valid?
end
Btw unless you use the instance variables somewhere else in the same
controller or its view then it should not be required assigning value to them first but you could pass the values directly to the mailer.
The correct update function in the controller:
def update
super
#email = resource.email
#event = resource.event
#name = resource.name
unless resource.errors.any?
NoticeMailer.notice_confirm(#email, #name, #event).deliver_later
end
end
Update02
def update
super
NoticeMailer.notice_confirm(
resource.email, resouce.name, resouce.event
).deliver_later unless resource.errors.any?
end

When using factory_bot (formerly factory_girl) with RSpec to test in Rails, undefined method "first"

I have an RSpec test that uses factory_bot to create instances. The test passes except when the first method is used in a view.
This is the code being tested:
def order_confirm_email(id, items, order, address, coupon)
#user = User.find(id)
#items = items
#order = order
#address = address
if coupon == nil
#coupon = ''
else
#coupon = coupon.discount
end
mail(to: #user.email, subject: 'Order completed')
end
This is the test:
it 'sends an email upon checkout process completion' do
user = create(:user)
items = create(:base_item)
order = create(:base_item)
address = create(:address)
coupon = create(:coupon)
expect(orderConfirmationMail.subject).to eq('Order completed')
end
So far so good. But when one of the views attempts to access the first instance, as below:
<h1>Order id - <%= #order.first.id %></h1>
Then I receive the following error:
Failures:
1) UserMailer user_emails sends an email upon checkout process completion
Failure/Error: <!-- <h1>Order id - <%= #order.first.id %></h1> -->
ActionView::Template::Error:
undefined method `first' for 3:Fixnum
According to my understanding, the instances should be persisting since I use create instead of build. But apparently that is not happening. Changing the view is not an option except as a last resort. How do I resolve this?
UPDATE 1:
This is the order_items.rb Factory file:
FactoryBot.define do
factory :base_item, class: OrderItem do
item_name_en "Sample Item"
item_link "http://www.foo.com"
qty 5
available_qty 100
item_price 10
seller_name "foo"
status "foo"
foo_item_id "123456"
foo_id "654321"
end
factory :order_item1, parent: :base_item do
foo_item_id "123456"
foo_seller_id "654321"
association :order
end
factory :order_item2, parent: :base_item do
foo_item_id "123457"
foo_seller_id "654321"
end
factory :order_item3, parent: :base_item do
foo_item_id "123458"
foo_seller_id "654322"
end
end
UPDATE 2:
I am encountering a similar issue when writing another test. I receive the following error:
Failures:
1) UserMailer user_emails sends an email upon abandoned cart
Failure/Error: <% #items.each do |item| %>
ActionView::Template::Error:
undefined method `each' for 1:Fixnum
This seems to indicate that it is not an issue with the first method per se, but rather with the interpolated Ruby in the views throwing an error when I run RSpec. This generalizes the problem and hopefully makes it more easily solvable.
that is not an issue, as earlier in the spec I include this line that I know is working correctly because I use the same format in another test successfully
let(:orderConfirmationMail) { UserMailer.order_confirm_email(1,2,3,4,nil) }
There it is. If you included this line from the start, your question would've been answered in two seconds.
Considering the signature of order_confirm_email
def order_confirm_email(id, items, order, address, coupon)
Why do you think order is set to 3? Because you pass it this way!
Mystery solved.
sorry, I don't have any idea. I would give a look to your OrderItem model, also I don't get why you do #order.first because that is just 1 object. Could make sense to me #orders.first but not #order.first. When things don't make sense in rails it is hard to build applications. also order and item should be two different models, we use order_items for building joins between two different models
An order can have many or just one item, you decide the relationship.
with rspec you can debug and also you can test your factories in the rails c test environment. There you can check what is the result of create(:base_item) or of FactoryGirl.create(:base_item)
I believe that should be an object, so I don't understand why it is saying undefined method 'first' for 3:Fixnum
Maybe the you factory is not what we expect
but you can understand this by debugging and testing in the console your factory

Rails validation is still firing despite unless option evaluating to true

I use devise_invitable in my app to allow users to send invitations. I realized a bad case in which a user has been invited but ignores the invitation and later returns to the app to sign up on their own. Because devise_invitable handles invitations by creating a new user using the provided email address for the invitation, my uniqueness validation on the email field will cause Rails to complain, telling the user that the email address is already taken.
I'm trying to write some logic to handle this case. I see two paths - either figure a way to detect this and destroy the previously created user and allow the new one to be created, or detect the user was invited and execute another flow. I've decided to implement the second option, as I'd like to still utilize the invitation if possible.
My limited experience has me questioning if what I've written will work, but I can't actually fully test it because the Rails validation on the email is triggered. I've made sure Devise's :validatable module is inactive. I created a method that (I think) will detect if a user was invited and in that case the uniqueness validation should be skipped.
#user.rb
...
validates :email, uniqueness: true, unless: :was_invited?
...
def was_invited?
if self.invitation_sent_at.present? && self.sign_in_count == 0
true
else
false
end
end
FWIW, I had originally written this in shorthand rather than breaking out the if/else, but I wanted to be very explicit in an effort to find the bug/failure.
The hope is that once the form passes validation, the create action will do some detection about a user's invitation status and, if they were invited, redirect them to the accept_user_invitation_path. Again, I haven't been able to actually test this yet because I can't get around the validations.
#registrations_controller.rb
def create
if User.find_by_email(params[:email])
#existing_user = User.find_by_email(params[:email])
#existing_user.save(validate: false)
if #existing_user.was_invited?
redirect_to accept_user_invitation_path(:invitation_token => #existing_user.invitation_token)
end
else
super
end
end
In a desperate effort, you'll see I've also added the .save(validate: false) to try to short circuit it there, but it's not even getting that far.
If I comment out the email validation entirely, simply to test the rest of the logic/flow, I get a PG error complaining on uniqueness because of an index on the email address - I don't want to tear all this apart simply to test this method.
I've tried to mess with this for hours and I'm at a loss - any help is appreciated. Let me know if there's any other code you want to see.
Looking at the redirect:
redirect_to accept_user_invitation_path(:invitation_token => #existing_user.invitation_token)
I can see that there is no return which should mean that if that redirect was being called you should be getting an AbstractController::DoubleRenderError error as the parent controller's create method should be trying to render the new view.
From this I would guess that the query you are using to find the existing user is not actually returning a result, possibly because you are using params[:email] whereas if you are using the default views or a properly formatted form it should be params[:user][:email].
Maybe you should give more responsibilities to your controller...
If you find the user, use that, else create a new one. Assuming your form appears with http://yourapp/users/new, change it in your routes to http://yourapp/users/new/:email, making the user input their email before advancing to the form.
def new
#existing_user = User.find_by_email("#{params[:email]}.#{params[:format]}") || User.new
if #existing_user.was_invited? # will only work for existing user
redirect_to accept_user_invitation_path(:invitation_token => #existing_user.invitation_token)
else
render 'new'
end
end
def create
# do maybe something before saving
if #existing_user.save(user_params)
# do your magic
else
render 'new', notice: "Oops, I didn't save"
end
end

Rails: How to enter value A in Model X only if value A exists in Model Y?

I'm trying to build a registration module where user can only register if their e-mail is already in an existing database.
Models:
User
OldUser
The condition on User will be
if OldUser.find_by_email(params[:UserName]) exists, allow user registration.
If not, then indicate error message.
This is really simple to do in PHP where I can just run a function to execute a mysql query. However, I couldn't figure out how to do it on Rails. It looks like I have to create a custom validator function but seems to be overkilled for a such simple condition.
It should be pretty simple to do. What have I missed?
Any pointer?
Edit 1:
This solution by dku.rajkumar works with a slight modification:
validate :check_email_existence
def check_email_existence
errors.add(:base, "Your email does not exist in our database") if OldUser.find_by_email(self.UserName).nil?
end
For cases like this, is it better to do validation in the model or at the controller?
you can do it as
if OldUser.find_by_email(params[:UserName])
User.create(params) // something like this i guess
else
flash[:error] = "Your email id does not exist in our database."
redirect_to appropriate_url
end
UPDATE: validation in model, so the validation will be done while calling User.create
class User < ActiveRecord::Base
validates :check_mail_id_presence
// other code
// other code
private
def check_mail_id_presence
errors.add("Your email id does not exist in our database.") if OldUser.find_by_email(self.UserName).nil?
end
end
I'd recommend starting with Devise.
See https://github.com/plataformatec/devise
Even if you have unusual needs like these, you can normally adapt it. Once you get to know it, it's extremely powerful, solid and debugged, and you can do all sorts of things with it.
Bellow is just an initial implementation .../app/controller/UsersController for User registration related actions.
def new
#user = User.new
end
def create
#user = User.new(params[:user])
#old_user = User.find_by_email(user.email)
if #old_user
if #user.save
# Handle successful save
else
render 'new' # and render some error message telling why registration was not succeed
end
else
# render some page with some sort of error message of 'new' new users
end
end
Update:
Check out the following resources for more info:
Ruby on Rails Tutorial
Rails: User/Password Authentication from Scratch, Part I/II

Problem with Active Record Querying

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.

Resources