I'm getting a "wrong number of arguments (2 for 1)" error when I try send a mailer to delayed_job. Everything works perfect if I don't try to push it to the background. Below is my controller with delayed_job:
def export
#user = User.where("id = ?", params[:user_id])
#logs = VehicleMileage.export_logs(params, #user)
if #logs['log_count'] > 0
ExportLogsMailer.delay.email_logs(#user, #logs['sending_to'])
end
respond_to do |format|
formats # index.html.erb
format.json { render json: #logs }
end
end
When I ExportLogsMailer.email_logs(#user, #logs['sending_to']).deliver the mailer works fine. I'm using the gem 'delayed_job_active_record' and gem 'rails', '3.2.13'.
Here is what ExportLogsMailer looks like:
class ExportLogsMailer < ActionMailer::Base
default :from => "support#myconsultantapp.com"
def email_logs(user, emails)
# make sure emails are unique
emails.uniq
first_name = user[0].first_name
last_name = user[0].last_name
# encoded_content = Base64.strict_encode64(File.read("#{Rails.root}/tmp/test.xls"))
# puts encoded_content
# attachments['test.xls'] = { :content => encoded_content,
# :encoding => 'Base64'
# }
# Name of template in your Mandrill template area
headers['X-MC-Template'] = 'Export Logs'
# Tags help classify your messages
headers['X-MC-Tags'] = 'mileage logs'
# Enable open or click-tracking for the message.
# Only can track to at a time. Possibilies: opens, clicks, clicks_htmlonly, clicks_textonly
headers['X-MC-Track'] = 'opens, clicks'
# Automatically generate a plain-text version of the email from the HTML content.
headers['X-MC-Autotext'] = 'true'
# Add dynamic data to replace mergetags that appear in your message content. Should be a JSON-formatted
# object and flat, so if you more than one recipient then add another X-MC-MergeVars to the header. The
# below example will change anywhere where *|FNAME|* or *|LNAME|* to the respective value.
mergeVars =
{
"fname" => first_name,
"lname" => last_name
}
headers['X-MC-MergeVars'] = mergeVars.to_json
# Add Google Analytics tracking to links in your email for the specified domains.
headers['X-MC-GoogleAnalytics'] = 'http://www.myconsultantapp.com/'
# Add an optional value to be used for the utm_campaign parameter in Google Analytics tracked links.
# headers['X-MC-GoogleAnalyticsCampaign'] = ''
# Information about any custom fields or data you want to append to the message.
# Up to 200 bytes of JSON-encoded data as an object. The object should be flat; nested object structures are not supported.
# headers['X-MC-Metadata'] = ''
# Whether to strip querystrings from links for reporting. "true" or "false"
# headers['X-MC-URLStripQS'] = 'true'
# Whether to show recipients of the email other recipients, such as those in the "cc" field. "true" or "false"
headers['X-MC-PreserveRecipients'] = 'false'
message = prepare_message :subject => "1 myConsultant logs",
:to => emails,
:content_type => "multipart/mixed"
message.alternative_content_types_with_attachment(
:text => 'text',
:html => 'html'
) do |i|
i.inline['test.xls'] = File.read("#{Rails.root}/tmp/test.xls")
end
message
end
end
Any help would be greatly appreciated. Thanks you!
Are you trying to retrieve just one user? Then do:
#user = User.find(params[:user_id])
Otherwise, if you're trying to pass an array of objects to delayed_job, I think you need to add all at the end:
#objects = Model.where(...).all
Related
I am trying to sending emails using MailGun's batch sending API using MailGun ruby sdk(https://github.com/mailgun/mailgun-ruby/blob/master/docs/MessageBuilder.md). As of now I have this method inside a class which inherits from ActionMailer.
class BatchMailer < ApplicationMailer
def send_batch_email(mail, recipients)
# First, instantiate the Mailgun Client with your API key
mg_client = Mailgun::Client.new("your-api-key")
# Create a Batch Message object, pass in the client and your domain.
mb_obj = Mailgun::BatchMessage.new(mg_client, "example.com")
# Define the from address.
mb_obj.from("me#example.com", {"first" => "Ruby", "last" => "SDK"});
# Define the subject.
mb_obj.subject("A message from the Ruby SDK using Message Builder!");
# Define the body of the message.
mb_obj.body_text("This is the text body of the message!");
# Loop through all of your recipients
mb_obj.add_recipient(:to, "john.doe#example.com", {"first" => "John", "last" => "Doe"});
mb_obj.add_recipient(:to, "jane.doe#example.com", {"first" => "Jane", "last" => "Doe"});
mb_obj.add_recipient(:to, "bob.doe#example.com", {"first" => "Bob", "last" => "Doe"});
...
mb_obj.add_recipient(:to, "sally.doe#example.com", {"first" => "Sally", "last" => "Doe"});
# Call finalize to get a list of message ids and totals.
message_ids = mb_obj.finalize
# {'id1234#example.com' => 1000, 'id5678#example.com' => 15}
end
end
Is is a correct way to keep the method that doesn't use actionmailer to send emails inside mailer?
ActionMailer method returns mail object but when trying to write spec for the method that uses API to send emails I can't able to get response as there won't be a mail object(ActionMailer message object). Where to keep this method and how it can be tested?
Is this a correct way to keep the method that doesn't use actionmailer to send emails inside mailer?
There is no reason to use a Mailer in this case. Simply use a service object (a plain-old ruby object or PORO). It might look something like:
class BatchMailerService
attr_accessor *%w(
mail
recipients
recipient
).freeze
delegate *%w(
from
subject
body_text
add_recipient
finalize
), to: :mb_obj
delegate *%w(
address
first_name
last_name
), to: :recipient, prefix: true
class << self
def call(mail, recipients)
new(mail, recipients).call
end
end # Class Methods
#==============================================================================================
# Instance Methods
#==============================================================================================
def initialize(mail, recipients)
#mail, #recipients = mail, recipients
end
def call
setup_mail
add_recipients
message_ids = finalize
end
private
def mg_client
#mg_client ||= Mailgun::Client.new(ENV["your-api-key"])
end
def mb_obj
#mb_obj ||= Mailgun::BatchMessage.new(mg_client, "example.com")
end
def setup_mail
from("me#example.com", {"first" => "Ruby", "last" => "SDK"})
subject("A message from the Ruby SDK using Message Builder!")
body_text("This is the text body of the message!")
end
def add_recipients
recipients.each do |recipient|
#recipient = recipient
add_recipient(
:to,
recipient_address,
{
first: recipient_first_name,
last: recipient_last_name
}
)
end
end
end
Which you would use something like:
BatchMailerService.call(mail, recipients)
(assuming, naturally, that you have variables called mail and recipients).
Where to keep this method?
You might place that file in app/services/batch_mailer_service.rb.
How can it be tested?
What do you mean? How you test the service depends on what your criteria for success are. You could test that mb_obj receives the finalize call (maybe using something like expect().to receive). You could test message_ids contains the correct information (maybe using something like expect().to include). It sort of depends.
I have a simple form on a website where a user enters a mailing address.
I have a service which can validate this address and return various responses, either Success, Suspect, or Invalid, as well as return the full and most complete zip code for that address. If the response is "Success", then I will save it to the db. If the response is "Suspect", then I will update the zip field and ask them to confirm. If the response is "Invalid" then I will return an error message asking them to contact us in person.
I'm trying to set up my rails create action such that it makes a call to my service (for example http:/addresssValidator.com) and I want to inform the user if they have a valid address or not, and update the zip with the suggested zip code.
Looking for address validation in rails however, seems to only give me APIs for using the built in error and validation system in rails and not how to return my own custom results to the form.
How can I do this?
Below is my code:
def create
#valid = validate_address
#address = Address.new(address_params)
if #valid
if #address.save
redirect_to "/survey/success"
else
p #address.errors
respond_to do |format|
format.json {render json: #address.errors}
format.html {render "/survey/failure"}
end
end
else
##//Display errors
end
end
def validate_address
#api_key = "my_api_key"
HTTParty.post("http://addressValidator.com",
{
:body => [
"StreetAddress" => params[:address][:address_line_2] + " " + params[:address][:address_line_1],
"City" => params[:address][:city],
"PostalCode" => params[:address][:zip],
"State" => params[:address][:state],
"CountryCode" => params[:address][:country],
"Locale" => "en" ].to_json,
:APIKey => #api_key ,
:headers => { 'Content-Type' => 'application/json', 'Accept' => 'application/json'}
})
return false;
##//return actual results from validator
end
If you want to add a custom error to a specific field:
#address.errors[:FIELD_NAME] << "Custom Error Message"
that way the error is added to the instance that you have, and will be shown in the form itself as the other errors appear.
if you just want to display an error, you can add it to the flash[:error] like this:
flash[:error] = "Invalid address"
if you want the error to move to the next method (like if you will use redirect after it), or:
flash.now[:error] = "Invalid address"
if you want the error to be available only in current action.
as #illusionist said in comment.
and in the html code, check if it exists, and if so, print it:
<% if flash[:error] %>
<%= flash[:error] %>
<% end %>
in both ways, you can add the error from #valid.
If you want to store the new zip, add #address.zip = #valid.zip. That way it will show it in the field in the form.
Models do the work, forms are stupid
In Rails you add errors to your models. To add an error you would do something like this:
if #address.country == "Sealand"
#address.errors[:country] << ["we don't ship there"]
end
In Rails forms are just are just simple form-builders bound to a model which create HTML. They don't actually have a role in validation besides displaying errors.
Custom validations
Rails lets you create custom validator classes and HTTParty is made to extend classes.
Lets mosh them together:
class AddressValidator < ActiveModel::Validator
include HTTParty
base_uri 'addressValidator.com'
format :json
def validate(address)
response = self.post('/',
# Note that I am just guessing here.
body: {
"StreetAddress" => address.street_address,
"City" => address.city,
"PostalCode" => address.zip,
"State" => address.state,
"CountryCode" => address.country
}
)
if response.success?
# bind the errors to the model
unless response["StreetAddress"] == "valid"
record.errors[:street_address] << response["StreetAddress"]
end
else
e = response.response
logger.warn "Failed to remotely validate Address. #{ e.message }"
address.errors[:base] << "Failed to remotely validate!"
end
end
end
Of course you will need to adapt this to the actual response from the API and your model. This is just a starting point demonstrating the coarse strokes.
So how would you use it?
def Address < ActiveRecord::Base
validates_with AddressValidator
# ...
end
http://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations
http://blog.teamtreehouse.com/its-time-to-httparty
Context:
I need to send bulk-email using send grid in a rails app.
I will be sending emails to maybe around 300 subscribers.
I have read that it can be accomplished using
headers["X-SMTPAPI"] = { :to => array_of_recipients }.to_json
I have tried following that.
The following is my ActionMailer:
class NewJobMailer < ActionMailer::Base
default from: "from#example.com"
def new_job_post(subscribers)
#greeting = "Hi"
headers['X-SMTPAPI'] = { :to => subscribers.to_a }.to_json
mail(
:to => "this.will#be.ignored.com",
:subject => "New Job Posted!"
)
end
end
I call this mailer method from a controller
..
#subscribers = Subscriber.where(activated: true)
NewJobMailer.new_job_post(#subscribers).deliver
..
The config for send-grid is specified in the config/production.rb file and is correct, since I am able to send out account activation emails.
Problem:
The app works fine without crashing anywhere, but the emails are not being sent out.
I am guessing the headers config is not being passed along ?
How can I correct this ?
UPDATE:
I checked for email activity in the send grid dashboard.
Here is a snapshot of one of the dropped emails:
You are grabbing an array of ActiveRecord objects with
#subscribers = Subscriber.where(activated: true)
and passing that into the smtpapi header. You need to pull out the email addresses of those ActiveRecord objects.
Depending on what you called the email field, this can be done with
headers['X-SMTPAPI'] = { :to => subscribers.map(&:email) }.to_json
In my app I am using the switch_user (https://github.com/flyerhzm/switch_user) gem to allow admins to login as another user. The gem has the ability to log back in as an admin, but I am having a hard time conceptualizing how to do it.
Here is my config:
SwitchUser.setup do |config|
# provider may be :devise, :authlogic, :clearance, :restful_authentication, :sorcery, or :session
config.provider = :devise
# available_users is a hash,
# key is the model name of user (:user, :admin, or any name you use),
# value is a block that return the users that can be switched.
config.available_users = { :user => lambda { User.all } }
# available_users_identifiers is a hash,
# keys in this hash should match a key in the available_users hash
# value is the name of the identifying column to find by,
# defaults to id
# this hash is to allow you to specify a different column to
# expose for instance a username on a User model instead of id
config.available_users_identifiers = { :user => :id }
# available_users_names is a hash,
# keys in this hash should match a key in the available_users hash
# value is the column name which will be displayed in select box
config.available_users_names = { :user => :email }
# controller_guard is a block,
# if it returns true, the request will continue,
# else the request will be refused and returns "Permission Denied"
# if you switch from "admin" to user, the current_user param is "admin"
config.controller_guard = lambda { |current_user, request, original_user|
current_user.school_admin? || original_user.school_admin?
}
# view_guard is a block,
# if it returns true, the switch user select box will be shown,
# else the select box will not be shown
# if you switch from admin to "user", the current_user param is "user"
config.view_guard = lambda { |current_user, request, original_user|
current_user.school_admin? || original_user.school_admin?
}
# redirect_path is a block, it returns which page will be redirected
# after switching a user.
config.redirect_path = lambda { |request, params| '/' }
# helper_with_guest is a boolean value, if it set to false
# the guest item in the helper won't be shown
config.helper_with_guest = true
# false = login from one scope to another and you are logged in only in both scopes
# true = you are logged only into one scope at a time
config.login_exclusive = true
# switch_back allows you to switch back to a previously selected user. See
# README for more details.
config.switch_back = true
end
Their README says you can have these links in your view
<%= link_to user.login, "/switch_user?scope_identifier=user_#{user.id}" %>
<%= link_to admin.login, "/switch_user?scope_identifier=admin_#{admin.id}" %>
But there is no way to load the "original user" to check to see if you need to display the admin login link.. anyone else have experience using this gem?
You can access original user from your controllers by doing
provider = SwitchUser::Provider.init(self)
provider.original_user
Cheers
I had similar issues with switch user and its switching back option, so at the end I am trying to implement something by myself.
I'm using this as a starting point, I hope it helps you as well.
I want to use this function from mongoid:
person.update_attributes(first_name: "Jean", last_name: "Zorg")
But I want to pass in all the attributes from another variable. How do I do that?
Edit: Thanks everyone for your reply. I'm new to ruby so at first I thought I just made a silly mistake with this. The bug was in a completely different place, the correct code, for your enjoyment:
def twitter
# Scenarios:
# 1. Player is already signed in with his fb account:
# we link the accounts and update the information.
# 2. Player is new: we create the account.
# 3. Player is old: we update the player's information.
# login with a safe write.
puts "twitter"
twitter_details = {
twitter_name: env["omniauth.auth"]['user_info']['name'],
twitter_nick: env["omniauth.auth"]['user_info']['nickname'],
twitter_uid: env["omniauth.auth"]['uid']
}
if player_signed_in?
#player = Player.find(current_player['_id'])
else
#player = Player.first(conditions: {twitter_uid: env['omniauth.auth']['uid']})
end
if #player.nil?
#player = Player.create!(twitter_details)
else
#player.update_attributes(twitter_details)
end
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Twitter"
sign_in_and_redirect #player, :event => :authentication
end
The update_attributes method takes a Hash argument so if you have a Hash, h, with just :first_name and :last_name keys then:
person.update_attributes(h)
If your Hash has more keys then you can use slice to pull out just the ones you want:
person.update_attributes(h.slice(:first_name, :last_name))
if you look at the source code of Mongoid, you'll see the definition of update_attributes in the file
.rvm/gems/ruby-1.9.2-p0/gems/mongoid-2.3.1/lib/mongoid/persistence.rb
# Update the document attributes in the datbase.
#
# #example Update the document's attributes
# document.update_attributes(:title => "Sir")
#
# #param [ Hash ] attributes The attributes to update.
#
# #return [ true, false ] True if validation passed, false if not.
def update_attributes(attributes = {})
write_attributes(attributes); save
end
It takes a Hash -- that means you can use a Hash as the variable that's passed in.
e.g.
my_attrs = {first_name: "Jean", last_name: "Zorg"}
person.update_attributes( my_attrs )
What's happening in the update_attributes method and, indeed, across the Rails platform is variables get put into a hash internally, when necessary.
So the following are equivalent:
person.update_attributes(first_name: "Jean", last_name: "Zorg")
person.update_attributes({first_name: "Jean", last_name: "Zorg"})
person.update_attributes(name_hash)
Where name_hash is:
name_hash = {first_name: "Jean", last_name: "Zorg"}