Alex Heil Alex Heil - 3 years ago 107
Ruby Question

Send email to only certain Users, Rails 5

I'm having trouble sending an email blast to only certain users who have a boolean set to true and to not send the email to those users who have it set to false.

In my app I have Fans following Artists through Artists Relationships. Inside my ArtistRelationship model I have a boolean that fans can set to true or false based on if they want email blasts from Artists or not when the Artist makes a post.

So far, I have this:

artist.rb

class Artist < ApplicationRecord

def self.fan_post_email
Artist.inlcudes(:fans).find_each do |fan|
fan.includes(:artist_relationships).where(:post_email => true).find_each do |fan|
FanMailer.post_email(fan).deliver_now
end
end
end

end


posts_controller.rb

class Artists::PostsController < ApplicationController

def create
@artist = current_artist
@post = @artist.artist_posts.build(post_params)
if @post.save
redirect_to artist_path(@artist)
@artist.fan_post_email
else
render 'new'
flash.now[:alert] = "You've failed!"
end
end

end


I'm having trouble getting the
fan_post_email
method to work. I'm not entirely sure how to find the Fans that have the boolean set to true in the ArtistRelationship model.

Answer Source

You want to send mails to fans of a particular artist. Therefore you call

@artist.fan_post_email

That is you call a method on an instance of the Artist class. Instance methods are not defined with a self.[METHOD_NAME]. Doing so defines class methods (if you where to call e.g. Artist.foo).

First part then is to remove the self. part, second is adapting the scope. The complete method should look like this:

def fan_post_email
  artists_relationships
    .includes(:fan)
    .where(post_email: true)
    .find_each do |relationship|
      FanMailer.post_email(relationship.fan).deliver_now
    end
  end
end

Let's walk through this method.

We need to get all fans in order to send mails to them. This can be done by using the artist_relationships association. But as we only want to have those fans having checked the e-mail flag, we limit those by the where statement.

The resulting SQL condition will give us all such relationships. But we do it in batches (find_each) in order to not have to load all of the records into memory upfront.

The block provided to find_each is yielded with an artists_relationships instance. But we need the fan instances and not the artists_relationships instances to send the mail in our block and thus call post_email with the fan instance associated with the relationship. In order to avoid N+1 queries (a query for the fan record of every artists_relationships record one by one) there, we eager load the fan association on the artists_relationships.


Unrelated to the question

The usage of that method within the normal request/response cycle of a user's request will probably slow down the application quite a lot. If an artists has many fans, the application will send an e-mail to every one of them before rendering the response for the user. If it is a popular artist, I can easily imagine this taking minutes.

There is a counterpart to deliver_now which is deliver_later (documentation. Jobs, like sending an e-mail, can be queued and resolved independent from the request/response cycle. It will require setting up a worker like Sidekiq or delayed_job but the increase in performance is definitely worth it.

If the queueing mechanism is set up, it probably makes sense to move the call to fan_post_email there as well as the method itself might also take some time.

Additionally, it might make sense to send e-mail as BCC which would allow you to send one e-mail to multiple fans at the same time.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download