Disable tracking for rails ahoy_email - ruby-on-rails

I've recently upgraded to ahoy_email version 2.0.3 from 1.1.1 and i've been refactoring my code to work with the latest version but i'm having a bit of an issue with a piece of functionality i used to have. The problem is the following:
I want to be able to disable tracking for some of the mails that get sent.
With the changes made to ahoy_email in version 2.0.0, all of the class methods that set the ahoy_settings perform add a before_action which merges the options set from on of the three methods, i.e. track_clicks, has_history and utm_params, so since the options only get merged in a before_action i can't do something like the following:
class DigestMailer < ActionMailer::Base
has_history user: -> { #recipient }, extra: -> { {digest_message_id: #digest_message.id} }
track_clicks campaign: "digest"
def digest(digest_message, user, **options)
#recipient = user
#digest_message = digest_message
self.class.has_history options
....
end
end
DigestMailer.digest(DigestMessage.first, User.first, click: false, message: false).deliver_now
The options i've set in the digest action wouldn't actually get merged, since they are supposed to get merged before calling the digest action
What i could do tho, is the following:
class DigestMailer < ActionMailer::Base
has_history user: -> { #recipient }, extra: -> { {digest_message_id: #digest_message.id} }
track_clicks campaign: "digest"
def digest digest_message, user, **options
#recipient = user
#digest_message = digest_message
self.ahoy_options = AhoyEmail.default_options.merge(options)
end
end
DigestMailer.digest(DigestMessage.first, User.first, click: false, message: false).deliver_now
This works, because i'm overriding the ahoy_options attribute after the before_action callback gets called and before the after_action callbacks get called and it works.
So my question is: Is this the right way to go about this?
Because this commit, from 3 years ago, clearly says:
Keep old method of disabling tracking
But it's nowhere to be found in the documentation.

To anyone who runs into the same problem, i've found the solution and it consists of two things:
Using a parametrized mailer and using the :if option of the track_clicks, has_history and utm_params. The code can be rewritten as the following:
class DigestMailer < ActionMailer::Base
has_history(
user: -> { params[:recipient] },
extra: -> { {digest_message_id: params[:digest_message].id} },
if: -> { params[:options].try(:fetch, :message) != false }
)
track_clicks(
campaign: "digest",
if: -> { params[:options].try(:fetch, :track_clicks) != false }
)
def digest
recipient = params[:recipient]
digest_message = params[:digest_message]
Rails.logger.info "Sending digest to #{recipient} with email #{recipient.data.email}"
internal_send_digest_message(digest_message.title)
end
end
DigestMailer.with(digest_message:DigestMessage.first, recipient: User.first, options: {track_clicks: false, message: false}).deliver_now
And it will work as expected.

Related

Rails stub method on initialized ActiveRecord object

I want to stub set_user_tokens which is executed on the initialized (not saved) ActiveRecord object. This method assigns a token to the login object.
class AwareLogin < Authenticatable
def authenticate!
login = Login.find_or_initialize_for_authentication(params[:login][:email], 'aware')
class << login
include AwareAuth
end
if login.valid_password?(password) || (set_token = login.id.nil? && aware_response.success?)
login.set_user_tokens(aware_response) if set_token
success!(login)
else
aware_response.success? ? fail!(:aware_auth) : raise(ActiveRecord::Rollback)
end
end
end
end
So I want to stub setu_user_tokens method:
login.set_user_tokens(aware_response) if set_token
to receive login ActiveRecord object with attributes of oauth_tokens like below:
login.oauth_token
=> {"access_token" => return_token,"refresh_token" => nil,"token_expiration" => 1200 }
I've tried:
allow_any_instance_of(Login).to receive(:set_user_tokens).with(status: 200, body: { access_token: return_token }.to_json, success?: true).and_return(
oauth_tokens: {
"access_token" => return_token,
"refresh_token" => nil,
"token_expiration" => 1200 },
)
But I'm getting an error:
Login does not implement #set_user_tokens
I would be willing to bet your issue is set_user_tokens is part of AwareAuth.
Since you are only including this module in the eigenclass of the instance (login) as part of the AwareLogin#authenticate! method, the Login class does not implement that method at any point in time.
Is there a reason you are doing it this way rather than just including AwareAuth in the Login class in the first place?
Either way, while your question appears to lack context for the test itself, if I understand correctly, we should be able to resolve these issues as follows:
it 'sets user tokens' do
login = Login
.find_or_initialize_for_authentication('some_email#example.com', 'aware')
.tap {|l| l.singleton_class.send(:include, AwareAuth) }
allow(Login).to receive(:find_or_initialize_for_authentication).and_return(login)
allow(login).to receive(:set_user_tokens).and_return(
oauth_tokens: {
"access_token" => return_token,
"refresh_token" => nil,
"token_expiration" => 1200 }
)
#perform your action and expectations here
end
By using partial doubles you can stub the specific methods you need to without impacting any other functionality of the object itself.

bulk email using send grid in rails

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

Rails Select2 Case-insensitive AJAX Autocomplete

I'm working on implementing autocomplete utilizing Select2 to get an AJAX loaded list of Users from JSON in order to fill a multi-value select box.
So far I've been able to implement most of the desired functionality by referencing the following sources:
http://gistflow.com/posts/428-autocomplete-with-rails-and-select2
http://luksurious.me/?p=46
My problem is that, the autocomplete query is case sensitive. I need it to be case-insensitive. Through a bit of research, I came across a GitHub issue where the Select2 creator explains that "Ajax matching should be done on server side."
https://github.com/ivaynberg/select2/issues/884
After much trial and error and some extensive research, I've come up with no solutions to solve the case-sensitivity issue. Unfortunately, "matching on the server side" is a bit over my head, and I was wondering if somebody might be able to suggest a solution to this problem?
What follows is my work so far:
Haml
= hidden_field :recipient_id, "", data: { source: users_path }, class: "select2-autocomplete"
CoffeeScript
$ ->
$('.select2-autocomplete').each (i, e) ->
select = $(e)
options = {
multiple: true
}
options.ajax =
url: select.data('source')
dataType: 'json'
data: (term, page) ->
q: term
page: page
per: 5
results: (data, page) ->
results: data
options.dropdownCssClass = 'bigdrop'
select.select2 options
Users Controller
class UsersController < ApplicationController
def index
#users = User.order('name').finder(params[:q]).page(params[:page]).per(params[:per])
respond_to do |format|
format.html
format.json { render json: #users }
end
end
end
User Model
class User < ActiveRecord::Base
scope :finder, lambda { |q| where("name like :q", q: "%#{q}%") }
def as_json(options)
{ id: id, text: name }
end
end
Figured it out!
The answer lies in the custom scope and the LIKE clause in the query. LIKE is a case-sensitive clause. Since, I'm using PostgreSQL, I was able to change the LIKE clause to ILIKE which is case-insensitive.
So in order to get case-insensitive matching, the User Model should look like the following:
User Model
class User < ActiveRecord::Base
scope :finder, lambda { |q| where("name ILIKE :q", q: "%#{q}%") }
def as_json(options)
{ id: id, text: name }
end
end

Methods not generated for Opinions with Mongoid

I'm trying to build an application on RoR that uses MongoDB via Mongoid for the main objects but has a like and dislike process using Redis via Opinions https://github.com/leehambley/opinions/.
It sort of works but when I run the methods on my objects I just get an error "undefined method `like_by'" where I think the methods are supposed to be autogenerated.
My model looks like:
class Punchline
include Mongoid::Document
include Opinions::Pollable
opinions :like, :dislike
field :key, type: String
field :text, type: String
field :won, type: Boolean
field :created, type: Time, default: ->{ Time.now }
field :score, type: Integer
index({ key: 1 }, { unique: true, name: "key_index" })
belongs_to :user
embedded_in :joke
end
and I run:
user = User.find(session[:userid])
#joke.punchlines.sample.like_by(user);
But it fails with the undefined method error :(
Do I need to initialize Opinions somewhere beyond
/config/initializers/opinions.rb
Opinions.backend = Opinions::RedisBackend.new
Redis.new(:host => 'localhost', :port => 6379)
So, it turns out that Opinions doesn't really work. Why is a bit beyond my two weeks with Rails :)
Anyway, it turns out that this is really easy to do by hand anyway especially as I only had a like and dislike to handle.
I used a Redis sorted set which allows a unique key - value pair with a score. I used a score of +1 or -1 to denote like or dislike and then encoded the key to represent the liked object and the value to be the user id.
This looked like:
def like(user)
$redis.zadd('joke:'+self.key+':likes', 1, user._id)
end
def dislike(user)
$redis.zadd('joke:'+self.key+':likes', -1, user._id)
end
def numLikes
res = $redis.zrangebyscore('joke:'+self.key+':likes',1,1);
return res.count
end
def numDislikes
res = $redis.zrangebyscore('joke:'+self.key+':likes',-1,-1);
return res.count
end
def likedBy(user)
res = $redis.zscore('joke:'+self.key+':likes',user._id)
return (res == 1)
end
def dislikedBy(user)
res = $redis.zscore('joke:'+self.key+':likes',user._id)
return (res == -1)
end

Mongoid insert embedded document to many documents

Let's say, I have two models
class User
embeds_many :notifications
field :age, type :Integer
class Notification
embedded_in :user
field :message, type: String
I want to create notification and add it to all users, matching specific criteria. All I came up with is:
notification = Notification.new
notification.message = "Hello"
User.where(:age.ge => 18).push(:notifications, notification)
But this won't work. Any idea?
UPD: I know, there's a way to make it work like so:
users = User.where(:age.ge => 18)
users.each do |user|
notification = Notification.new
notification.message = "Hello"
user.notifications << notification
user.save
end
But this seems ugly and inefficient code.
UPD2: Finally, this code works, directly working with driver, as Winfield stated:
users = User.where(:age.ge => 18)
notification = Notification.new
notification.message = "Hello"
User.collection.update(users.selector, {'$push' => {notifications: notification.as_document}}, multi: true)
You can probably do this at the raw Mongo Driver level:
db.users.update({}, { $push: { notifications: { message: 'Hello!' } } })
However, if your goal is to accomplish a mass-messaging feature, you might be better off making a special collection for these Notifications and having your application code pull system-wide notifications and user-targeted notifications.
Having to update every single user object in your system to send a mass message is not a scalable design.

Resources