Search code examples
arraysrubyloopsmysql2ruby-hash

How to use values of a hash dynamically?


Goal: Use unique email address in email header To: field (see message_class) of MIME Html Message.

File: database.rb Here I connect to the database and I loop through the rows in table rcpt. Then I store the value in row["email"].

require 'mysql2'

class Db

  def con
    @db_host = "localhost"
    @db_user = "root"
    @db_pass = "password"
    @db_name = "table_db"

    client = Mysql2::Client.new(:host => @db_host, :unsername => @db_user, :password => @db_pass, :database => @db_name)
    rcpt = client.query("SELECT * from rcpt")
    rcpt.each do |row|
      row["email"]
    end
  end
end

Ouput of con method without ["email"]:

{"id"=>01, "email"=>"example1@example.com"}
{"id"=>02, "email"=>"example2@example.com"}

Output of con method with ["email"]:

example1@example.com
example2@example.com

DB Schema:

mysql> describe rcpt;
+-------+--------------+------+-----+---------+----------------+
| Field | Type         | Null | Key | Default | Extra          |
+-------+--------------+------+-----+---------+----------------+
| id    | int(11)      | NO   | PRI | NULL    | auto_increment |
| email | varchar(150) | NO   | UNI | NULL    |                |
+-------+--------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

File: message_class.rb Here I create the method pull_rcpt which then stores row["email"]. Then I'm trying to use the pull_rcpt method in message heredoc. Now comes my problem: in the To: field, I'm trying to pass the users email address I'm currently sending to, so it should change with the receiving email address. It still uses the email address of the first contact in the array/Db. What am I doing wrong?

require 'dkim'
require './database'

class Email

  def pull_rcpt
    rcpt = Db.new
    rcpt.con
  end

  def data
    Dkim::domain      = 'example.com'
    Dkim::selector    = 'mail'
    Dkim::private_key = open('/path/to/keys/example.com/mail.private').read 

    message = <<~MESSAGE
    From: Eva <test@example.com>
    To: Dani <#{pull_rcpt[0]["email"]}>
    MIME-Version: 1.0
    Content-Type: text/html
    Content-Transfer-Encoding: 8bit
    Subject: Test Subject

    This is an email message.
    <h1>Test</h1>
    MESSAGE
   end
end

File: mailer_class.rb In my mailer_class I have two method's rcpt_to and message. Rcpt_to contains an array contacts, which holds the values of row["email"]. Message builds the email.

require './database'
require './message_class'

def rcpt_to
#conn zu DB & take rcpt
  contacts = []
  contacts = Db.new
  contacts.con
end

def message 
#message
  message = Email.new
end

A few lines later inside a for loop I use the methods as follows:

 for rcpt in rcpt_to do
  @protocol = { ehlo: "ehlo", mail_from: "eva@example.com", rcpt_to: [rcpt["email"]], data: Dkim.sign(message.data) }
 end

Here is the header of example2@example.com which is the second record in the database:

 From: Eva <test@example.com>
 To: Dani <example1@example.com> <--- This should be example2(value in hash of second entry in database) not example1
 MIME-Version: 1.0
 Content-Type: text/html
 Content-Transfer-Encoding: 8bit
 Subject: Test Subject
 Content-Length: 40

So the only thing not working is the dynamic To: field in the email header. The rest is just working fine and all records in the database receive an email.


Solution

  • As already mentioned, this line is the core of the problem: <#{pull_rcpt[0]["email"]}>. You are explicitly selecting the first element in the database. To create the header dynamically, you will have to pass an argument to the data-method.

    So here's one way to solve this:

    In class Email, change def data to def data(dynamic_email), and To: Dani <#{pull_rcpt[0]["email"]}> to To: Dani <#{dynamic_email}>

    and within the loop: data: Dkim.sign(message.data) => data: Dkim.sign(message.data(rcpt["email"]))

    Something like that should work.

    As a side note, your code could do some refactoring. For example, the con-method you have written doesn't work the way you think. "each" returns the array itself. If you put it on the last line of a method like that, it doesn't serve any purpose. In this case, it would be equivalent to writing return rcpt. If you want the method to return an array of email addresses, you should use map instead of each (or even better, rewrite your sql query). But of course, if you change that, it'll break the rest of your code. The lazy solution is just to remove the each-loop...