[Rails] Action Mailerのdelivery_methodを動的に切り替える方法

SendGridのAPI経由のEメール送信がテンプレートを使えて非常に良いので切り替えたいが、現在smtpで送っているメールをすべて一回で切り替えるのは不可能なので、同時運用したい。 下記のように、delivery_methoddelivery_method_optionsをmailメソッドに入れるとwrapしてくれるので、smtpを使わなくなる。

  def send_test_mail
    mail(
      to: 'myemail@change.this.com', 
      subject: 'email subject', 
      body: 'not used',
      template_id: "xxxx",
      delivery_method: :sendgrid_actionmailer, 
      delivery_method_options: {
        api_key: ENV['SENDGRID_API_KEY'] 
      }
    )
  end

[Rails5.2] rails-ujsのdata-confirmをoverrideしてdialogをmodalに切り替える方法

以前のRailsはjquery-ujsを使っていたみたいで、rails-ujsでのdata-confirmダイアログの変更方法が見つからなかったのでjquery-ujsのoverride方法を参考にしながら、試行錯誤しつつdata-confirmmの挙動を変更しました。showConfirmDialog内をお好きなmodalモジュールに変更すれば動くはず。今回はUIKitを使っていたのでそれを使いました。

var handleConfirm = function(element) {
    if (!allowAction(this)) {
      Rails.stopEverything(element)
    }   
  }
  var allowAction = function(element) {
    if (element.getAttribute('data-confirm') === null) {
      return true
    }   
    showConfirmDialog(element);
    return false
  }
  var confirmed = function(element, result) {
    if (result.value) {
      // User clicked confirm button
      element.removeAttribute('data-confirm')
      element.click()
    }   
  }
  var showConfirmDialog = function(link) {
    var message = $(link).attr('data-confirm');
    UIkit.modal.confirm(message).then(function(){
      confirmed(link, {value: true});
    }); 
  }
  $("a[data-confirm]").on('click',handleConfirm);

[Rails] sitemap_generatorのsitemap作成でAws::S3::Errors::AccessDenied: Access Denied

sitemap_generator gem

sitemap作成でハマったのでメモ。Herokuで動的なsitemapを作る一つの方法として、s3を使ったsitemap配信があります。ググれば、たくさんブログなどあるのですが、aws-sdkのバージョン2を使ったものがメイン。今回はaws sdk version3で構築してたのでAws::S3::Errors::AccessDenied: Access Deniedが出て時間を無駄に消費しました。

require 'aws-sdk-s3'が必要なのはissueを漁ればわかりました。

#config/sitemap.rb
require 'aws-sdk-s3'
SitemapGenerator::Sitemap.sitemaps_host = "https://s3-ap-northeast-1.amazonaws.com/YOUR_BACKET/"
SitemapGenerator::Sitemap.adapter = SitemapGenerator::AwsSdkAdapter.new(
  "YOUR_BACKET",
  aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
  aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  aws_region: 'ap-northeast-1' 
)

Aws::S3::Errors::AccessDenied: Access Deniedですが、s3のバケットをpublicにしていないと出ていました。なので、コンソールから下記を追加すればOK。

Rails経由でAWS Lambda(ruby)を使いファイルをs3にアップロードする方法

タイトルが渋滞していますが、、やったことは下記のこと。

  1. Railsが非同期ジョブ走らせる
  2. 非同期ジョブはlambdaをinvokeする
  3. lambdaがcurlでファイルを持ってきて、s3にアップロードする
  4. その結果のアップロード先URLをRailsのDBに保存

AWS Lambdaを触ったのは初めてです。nodeではなくrubyで動かします。まずは、AWS上にLambda関数を作ります。今回は、コードは短いだろうと予想し、ローカルにインストールせずにAWSのWEB UIのエディタを使いました。

Role 追加

登場ロールは2つ、Rails側でlambdaをinvokeするロールとlambda関数を走らせるロール

Rails側でlambdaをinvokeするロール

*全部必要ではないかもしれませんが、面倒なので全て権限をあげました。

lambda関数を走らせるロール

Rails側のコード

AWS Ruby SDK version2 です。RequestResponseだと同期で処理します。返り値を拾うためにもここは同期で処理。invoke_copy自体を非同期で走らせるイメージです。

  def invoke_copy(recording_id, download_url, object_path)
    req_payload = {
      download_url: download_url, 
      object_path: object_path, 
    }
    payload = JSON.generate(req_payload)
    resp = lambda.invoke({
      function_name: "copyToS3", # required
      invocation_type: "RequestResponse", # accepts Event, RequestResponse, DryRun
      log_type: "Tail", # accepts None, Tail
      payload:  payload,
    })
    logger.debug Base64.decode64(resp.log_result)
    res = JSON.parse(resp["payload"].read());
    logger.debug res
    self.update(url: res["body"])
  end

  def lambda
    @lambda ||= Aws::Lambda::Client.new(
      region: "ap-northeast-1",
      access_key_id: ENV['AWS_ACCESS_KEY_ID'], 
      secret_access_key: ENV['AWS_SECRET_KEY'] 
    )
  end

Lambda関数のコード

AWS Ruby SDK version3です。Lambdaの/tmpのみがwritableな場所で、maxで512MBです。 したがって、これ以上コピーできません。

require 'json'
require 'aws-sdk-s3'

def lambda_handler(event:, context:)
    puts event
    pp ENV
    puts `curl -L #{event["download_url"]} -o /tmp/a`
    puts `ls -al /tmp/a`
    puts `file /tmp/a`
    s3 = Aws::S3::Resource.new
    obj = s3.bucket('my-backet').object(event["object_path"])
    obj.upload_file('/tmp/a')
    
    { statusCode: 200, body: obj.public_url }
end