I'm following this Railscast here for sending mails from my rails app.
At first I tried to set up a mail interceptor like in the tutorial, but I've given up on that, because I always got a lovely can't convert nil into Hash (TypeError).
Now that I left out the interceptor, I wanted to actually send a mail, but guess what - my old friend is back:
can't convert nil into Hash (TypeError)
app/mailers/user_mailer.rb:20:in `registration'
Here's the mailer class (line #20 is the one with the call to mail):
class UserMailer < ActionMailer::Base
def registration( user )
#invited = user
subject = 'Welcome!'
if Rails.env.development?
to = 'my.private#email.com'
subject = "to #{#invited.email}: #{subject}"
else
to = #invited.email
end
logger.info "Sending email to #{to} with subject #{subject}"
mail( :from => 'noreply#mysite.com', :to => to, :subject => subject )
end
end
The log shows:
invite entered for user foo#bar.com
Sending email to foo#bar.com
Sending email to my.private#email.com with subject to foo#bar.com: Welcome!
And here's the requested view file (registration.text.erb):
Welcome!
blablah blah click the following link to activate blah blah <%= activate_user_path( #invited )%> blah blah.
blah blah this is actually german (but plaintext) encoded in utf-8.
Viel Vergnügen beim Verwenden des Systems!
thx for any help
did it, I had an error in actionmailer's config
Related
i am working on rails3 application. In my application when a user registered first time, an email has been sent to user with a verification link, after clicking that link i update the status_id and redirect user to login page. Here is my code :
code for token generation:
require 'digest/sha2'
class Subscription < ActiveRecord::Base
validate :ids_must_be_present, :on => :create
def ids_must_be_present
if status_id==0
generate_token
else
errors.add('Something gone wrong')
end
end
def generate_token
self.token = encrypt_string(id, generate_salt)
end
def encrypt_string(id, salt)
Digest::SHA2.hexdigest(id.to_s + "prftnxt" + salt)
end
private
def generate_salt
self.object_id.to_s + rand.to_s + company_id.to_s + Time.now.to_i.to_s
end
end
code to send email with link:
def email_verify
if subscription = Subscription.find_by_id_and_token(params[:id], params[:token])
subscription.update_attribute(:status_id, 1)
redirect_to("/login/index", :notice => "Thanks, email successfully verified")
else
flash.now[:notice] = "Your email has not verified yet. Please verify your email by clicking the link we have sent you."
end
end
Email template with verification link:
Hello <b><%= #user.username %></b>,
<br/>
<br/>
Thank you for signing up .
<b> Please Verify your email</b>
<%= link_to "verify", "http://localhost:3000/login/email_verify?token=#{#subscription.token}&id=#{#subscription.id}"%>
</br></br>
</br>
Now everything is fine, now my client want if user did not get verification email, then we some where give the option or link to request to resend verification mail.
i am thinking on to display flash msg on login attempt with a link to request for email.
but i am confused how do i do this any example or help would be helpful thanks.
Hi friends i got a solution, i have used a method in login controller that check the email is verified or not and if not verified a flash message displayed. The message contains the link .
When a user click on that link i resend the verification mail.
Here is my code:
subscription = Subscription.find_by_company_id_and_plan_id(current_company.id, current_company.plan.id)
link = "<a href= '/login/resend_verification_email'>Click</a>"
if subscription.status_id == 0
flash[:error] = "Your email is not verified. Please verify before login. <br/> #{link} here to resend verification email.".html_safe
redirect_to :back
end
and in login controller:
def resend_verification_email
subscription = Subscription.find_by_company_id_and_plan_id(current_company.id, current_company.plan.id)
Email.verify_email(current_user, subscription).deliver
redirect_to :back
flash[:success] = 'Verification email has been resend successfully, please check your inbox.'
end
The example sorcery code shown on github appears to me to create duplicate accounts if it is extended to allow for multiple sign in methods (which is the whole point of oauth). You can see in the snipit here that create_from() will be called if login_from() does not succeed.
GITHUB AT at https://github.com/NoamB/sorcery-example-app/blob/master/app/controllers/oauths_controller.rb
def callback
provider = params[:provider]
begin
if #user = login_from(provider)
redirect_to root_path, :notice => "Logged in from #{provider.titleize}!"
else
begin
#user = create_from(provider)
Investigating the source code for create_from in all cases a new User Account record will be created. This would not be correct, if a User account record already exists.
My question: What sorcery methods should be called on the first facebook connect, if a User account has been created by some means other than facebook. login_from will fail, and create_from will generate a duplicate usser record?
You can use def create_and_validate_from(provider).
It will validate if the users email/username already exist. If its true, that he will store infos into a session and can be rendered into registration form.
And if you wish to add some provider to your account you can use def add_provider_to_user(provider).
Several requests have come through for an answer to this question, so I am providing the answer that Andy Mejia part of my team eventually arrived at for this question. We used the source within sorcery to adapt the following functions:
# Returns the hash that contains the information that was passed back from Facebook.
# It only makes sense to call this method on the callback action.
#
# Example hash:
# {:user_info=>{:id=>"562515238", :name=>"Andrés Mejía-Posada", :first_name=>"Andrés", :last_name=>"Mejía-Posada", :link=>"http://www.facebook.com/andmej", :username=>"andmej", :gender=>"male", :email=>"andmej#gmail.com", :timezone=>-5, :locale=>"en_US", :verified=>true, :updated_time=>"2011-12-31T21:39:24+0000"}, :uid=>"562515238"}
def get_facebook_hash
provider = Rails.application.config.sorcery.facebook
access_token = provider.process_callback(params, session)
hash = provider.get_user_hash
hash.merge!(:access_token => access_token.token)
hash.each { |k, v| v.symbolize_keys! if v.is_a?(Hash) }
end
# Method added to the User Account model class
def update_attributes_from_facebook!(facebook_hash)
self.first_name = facebook_hash[:user_info][:first_name] if self.first_name.blank?
self.last_name = facebook_hash[:user_info][:last_name] if self.last_name.blank?
self.facebook_access_token = facebook_hash[:access_token]
self.email ||= facebook_hash[:user_info][:email]
unless facebook_authentication?
authentications.create!(:provider => "facebook", :uid => facebook_hash[:uid])
end
self.build_facebook_profile if facebook_profile.blank?
save!
self.facebook_profile.delay.fetch_from_facebook! # Get API data
end
To show these code in context, I am also including logic from our controller:
def callback
provider = params[:provider]
old_session = session.clone # The session gets reset when we login, so let's backup the data we need
begin
if #user = login_from(provider) # User had already logged in through Facebook before
restore_session(old_session) # Cleared during login
else
# If there's already an user with this email, just hook this Facebook account into it.
#user = UserAccount.with_insensitive_email(get_facebook_hash[:user_info][:email]).first
# If there's no existing user, let's create a new account from scratch.
#user ||= create_from(provider) # Be careful, validation is turned off because Sorcery is a bitch!
login_without_authentication(#user)
end
#user.update_attributes_from_facebook!(get_facebook_hash)
rescue ::OAuth2::Error => e
p e
puts e.message
puts e.backtrace
redirect_to after_login_url_for(#user), :alert => "Failed to login from #{provider.titleize}!"
return
end
redirect_to after_login_url_for(#user)
end
I hope this solution is helpful to others.
I came across the same problem. While I have not found a direct solution via Sorcery, I did the following which seems to work:
#user = create_from(params[:provider]) do |user|
User.where(:twitter_id => user.twitter_id).first.blank?
end
This teqnique requires that you have twitter_id in the User model. You can also do it the other way around with the Authentication model instead. Such as:
#user = create_from(params[:provider]) do |user|
Authentication.where(:uid => user.twitter_id).first.blank?
end
If the block returns false, then it doesn't create the user. Avoiding any duplicates.
Note, the block for create_from does not work with 0.7.12. It works with 0.7.13.
We have a problem in our RoR app. We are using a facebook authentication with omniauth, and searching the user friends with Koala. But lately, when we try to show a friend photo, we got this error:
Koala::Facebook::APIError in Homes#show
Showing /home/daniel/Homes/app/views/shared/_event.html.erb where line #19 raised:
OAuthException: Error validating access token: Session has expired at unix time 1328727600. The current unix time is 1328802133.
Extracted source (around line #19):
16: <img src="../assets/friends-icon.png" alt="User profile apicture" height="33" width="43">
17: <% if current_user %>
18: <% event.friends_in_event(#person).each do |f| %>
19: <%= link_to(image_tag(f.fb_picture, :size => "43x33"), person_path(f.id)) %>
20: <% end %>
21: <% end %>
22: </div>
The authentication works good, but facebook has already deprecated the offline_access option, that was working good, but now, we have this issue.
is It any way to extends the access_token?, or are there another solution?.
This is our omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, ENV['FB_KEY'], ENV['FB_SECRET'],
{ :scope => 'email,offline_access,user_photos,publish_stream',
:client_options => { :ssl => { :ca_path => "/etc/ssl/certs" } } }
end
And our koala.rb
Koala.http_service.http_options = {
:ssl => { :ca_path => "/etc/ssl/certs" }
}
Thanks in advance.
There are 2 solutions to this problem:
Extend the user's access token:
As per this article on the Facebook docs, you may request a 60-day extension on a user's access token. However, if the user does not return within that period, this method won't help you.
You can find a PHP code snippet to do this at this StackOverflow question.
To do this, send a post to this API endpoint: https://graph.facebook.com/oauth/access_token?client_id=APP_ID&client_secret=APP_SECRET&grant_type=fb_exchange_token&fb_exchange_token=EXISTING_ACCESS_TOKEN
Catch the OAuthException and request a new access token:
Facebook provides a PHP code snippet outlining this solution on their dev blog.
Basically, you follow these steps:
Make a call to the graph with the user's current access_token.
If the call succeeds, the access_token is fine. If it throws an OAuthException, redirect the user to https://www.facebook.com/dialog/oauth?client_id=APP_ID&redirect_uri=CALLBACK_URL
The user will be sent to that URL and then redirected to your CALLBACK_URL with a code in the parameters.
Send a post to the following URL with the code to obtain a new access_token: https://graph.facebook.com/oauth/access_token?client_id=APP_ID&redirect_uri=CALLBACK_URL&client_secret=APP_SECRET&code=CODE&display=popup
Read the post on their dev blog for more information.
Edit (adding example Ruby on Rails code):
Add the following to the top of your ApplicationController:
rescue_from Koala::Facebook::APIError, :with => :handle_fb_exception
Add the following protected method to your ApplicationController:
def handle_fb_exception exception
if exception.fb_error_type.eql? 'OAuthException'
logger.debug "[OAuthException] Either the user's access token has expired, they've logged out of Facebook, deauthorized the app, or changed their password"
oauth = Koala::Facebook::OAuth.new
# If there is a code in the url, attempt to request a new access token with it
if params.has_key? 'code'
code = params['code']
logger.debug "We have the following code in the url: #{code}"
logger.debug "Attempting to fetch a new access token..."
token_hash = oauth.get_access_token_info code
logger.debug "Obtained the following hash for the new access token:"
logger.debug token_hash.to_yaml
redirect_to root_path
else # Since there is no code in the url, redirect the user to the Facebook auth page for the app
oauth_url = oauth.url_for_oauth_code :permissions => 'email'
logger.debug "No code was present; redirecting to the following url to obtain one: #{oauth_url}"
redirect_to oauth_url
end
else
logger.debug "Since the error type is not an 'OAuthException', this is likely a bug in the Koala gem; reraising the exception..."
raise exception
end
end
The Koala calls were all taken from the following 2 tutorials:
https://github.com/arsduo/koala/wiki/OAuth
https://github.com/arsduo/koala/wiki/Koala-on-Rails
For those of you who don't have time to make this change, I found that you can disable this migration in Settings -> Advanced. The name of the option is "Remove offline_access permission:"
I tried using this How do I test Pony emailing in a Sinatra app, using rspec? to test a Rails 3.1 app sending emails. The sending works fine, but I'm having a hard time getting the tests to work. Here's what I have so far ...
spec/spec_helper.rb
config.before(:each) do
do_not_send_email
end
.
.
.
def do_not_send_email
Pony.stub!(:deliver) # Hijack to not send email.
end
and in my users_controller_spec.rb
it "should send a greeting email" do
post :create, :user => #attr
Pony.should_receive(:mail) do |params|
params[:to].should == "nuser#gmail.com"
params[:body].should include("Congratulations")
end
end
and I get this ...
Failures:
1) UsersController POST 'create' success should send a greeting email
Failure/Error: Pony.should_receive(:mail) do |params|
(Pony).mail(any args)
expected: 1 time
received: 0 times
# ./spec/controllers/users_controller_spec.rb:121:in `block (4 levels) in '
It looks like Pony's not getting an email, but I know the real email is getting sent out.
Any ideas?
Here's what I finally ended up with for the test ...
it "should send a greeting email" do
Pony.should_receive(:deliver) do |mail|
mail.to.should == [ 'nuser#gmail.com' ]
mail.body.should =~ /congratulations/i
end
post :create, :user => #attr
end
The Pony.should_rececieve needs :deliver (not :mail), the do/end was changed a bit, and the post was done after the setup.
Hope this helps someone else.
I know this is an old question but there is another way to test this. Version 1.10 of Pony added override_options. Pony uses Mail to send email. override_options lets you use the TestMailer functionality that is built into Mail. So you can set up your test like this:
In spec_helper
require 'pony'
Pony.override_options = { :via => :test }
In your test
before do
Mail::TestMailer.deliveries.clear
end
it 'some test' do
# some code that generates an email
mail = Mail::TestMailer.deliveries.last
expect(mail.to).to eql 'some#email.com'
end
I am successfully generating and sending an email with the following code.
class UserMailer < ActionMailer::Base
default :from => 'user#user.com',
:date => Time.now
def new_user(user)
mail_subject = ['WELCOME TO ACME, INC', 'USER ACTIVATION']
#user = user
mail.attachments['File One.pdf'] = File.read(File.join(ATTACHMENT_DIR, 'shared', 'file_one.pdf'))
mail.attachments['File Two.pdf'] = File.read(File.join(ATTACHMENT_DIR, 'shared', 'file_two.pdf'))
mail.attachments['File Three.pdf'] = File.read(File.join(ATTACHMENT_DIR, 'shared', 'file_three.pdf'))
mail.attachments['File Four.pdf'] = File.read(File.join(ATTACHMENT_DIR, 'shared', 'file_four'))
mail( :to => user.address.email,
:subject => mail_subject.join(' ~ ').upcase )
end
end
However, the email contains three text documents which are identical to the content of the email body. The view I'm using for the mailer is named new_user.text.erb.
I suspect that for each pdf document I'm attaching, a plain text document is generated as well, the first being the actual email document body and the remaining three are attached along with the pdf documents.
How may I attach these pdf documents without also attaching these (repeating) text documents? Has anyone else run into this?
Try to use attachments['..'] instead of mail.attachments['..], it works here this way, and no duplicates are observed.
I'm on Rails 3.0.7, mail 2.2.19:
The only other difference that I see, is that I've a hash with mime_type and content. But I think that it worked the other way as well, it just was assigning a mime-type which was not adequate.
attachments['event.ics'] = {:mime_type=>'text/calendar', :content => ics}
mail({
:to => email,
:subject => subject,
}) do |format|
format.text { render :inline => mail_template }
...
end
I'm on Rails version 5.2.4.5. Hope it will help:
My reference comes from here 2.3.1 Adding Attachments and Attachments.
If you want to attach multiple files.
Just call this Method repeatedly. And it will send the mail with two file.
attachments["YourFile.csv"] = {mime_type: 'text/csv', content: csv_data}
attachments["YourFile2.csv"] = {mime_type: 'text/csv', content: csv_data}
This is my example that generate CSV and PDF and mail them in same time for your reference:
headers = ['Name', 'Age', 'Party', 'Vote']
csv_data = CSV.generate(headers: true) do |csv|
csv << headers
#candidates.each do |people|
csv << [people.name, people.age, people.party, people.vote_logs_count]
end
end
attachments["Report.csv"] = {mime_type: 'text/csv', content: csv_data}
attachments["Report2.pdf"] = WickedPdf.new.pdf_from_string(
render_to_string(:pdf => "pdf",:template => 'candidates/pdf.html.erb')
)
mail(
from: "SomeOne" + "#SomeHashDomain.mailgun.org",
to: 'Recipient#Domain.com',
subject: "CSV and PDF report"
)
Supplementary note:
I use WickedPdf Gem to generate PDF.