2021/3/7 activity_notification
を読んでいきます。
これは何?
通知周りの gem です
https://raw.githubusercontent.com/simukappu/activity_notification/images/activity_notification_plugin_focus_with_subscription.png
テーブル
notifications, subscriptiopns の 2 テーブル
code:ruby
create_table :notifications do |t|
t.belongs_to :target, polymorphic: true, index: true, null: false
t.belongs_to :notifiable, polymorphic: true, index: true, null: false
t.string :key, null: false
t.belongs_to :group, polymorphic: true, index: true
t.integer :group_owner_id, index: true
t.belongs_to :notifier, polymorphic: true, index: true
t.text :parameters
t.datetime :opened_at
t.timestamps null: false
end
レコードを作るのは
code:lib/activity_notification/models/concerns/notifiable.rb
def notify(target_type, options = {})
Notification.notify(target_type, self, options)
end
これがイベントが発生したときの通知
code:lib/activity_notification/apis/notification_api.rb
def notify(target_type, notifiable, options = {})
notify_later(target_type, notifiable, options)
else
unless targets.blank?
notify_all(targets, notifiable, options)
end
end
end
notify_all が呼ばれる
code:lib/activity_notification/apis/notification_api.rb
def notify_all(targets, notifiable, options = {})
notify_all_later(targets, notifiable, options)
else
targets.map { |target| notify_to(target, notifiable, options) }
end
end
これが全体宛
map で notify_to を呼んでいる
map なので返り値があると思うが、おそらく生成した Array<Notification, nil> を返しそう
片っ端から notify_later 対応してる
code:lib/activity_notification/apis/notification_api.rb
def notify_to(target, notifiable, options = {})
notify_later_to(target, notifiable, options)
else
send_email = options.has_key?(:send_email) ? options:send_email : true send_later = options.has_key?(:send_later) ? options:send_later : true # Generate notification
notification = generate_notification(target, notifiable, options)
# Send notification email
if notification.present? && send_email
notification.send_notification_email({ send_later: send_later })
end
# Publish to optional targets
if notification.present? && publish_optional_targets
end
# Return generated notification
notification
end
end
これが個人宛。1 件の Notification 作成
notification を作った後に、email や optional_targets (Slack や Amazon SNS 等) に通知を送る
code:rb
def generate_notification(target, notifiable, options = {})
key = options:key || notifiable.default_notification_key if target.subscribes_to_notification?(key)
# Store notification
notification = store_notification(target, notifiable, key, options)
end
end
generate_notification は、 subscribe していない場合に通知を送らない
code:ruby
# Stores notifications to datastore
# @api private
def store_notification(target, notifiable, key, options = {})
target_type = target.to_class_name
group = options:group || notifiable.notification_group(target_type, key) group_expiry_delay = options:group_expiry_delay || notifiable.notification_group_expiry_delay(target_type, key) notifier = options:notifier || notifiable.notifier(target_type, key) parameters.merge!(options.except(*available_options))
parameters.merge!(notifiable.notification_parameters(target_type, key))
group_owner = valid_group_owner(target, notifiable, key, group, group_expiry_delay)
notification = new({ target: target, notifiable: notifiable, key: key, group: group, parameters: parameters, notifier: notifier, group_owner: group_owner })
notification.prepare_to_store.save
notification.after_store
notification
end
new して prepare_to_store は self を返して save している
ActiveRecord 以外に対応するためにこういう作りになっていそう
実際自分でも Dynamo 使いそう
各カラムの意味
code:ruby
create_table :notifications do |t|
t.belongs_to :target, polymorphic: true, index: true, null: false
t.belongs_to :notifiable, polymorphic: true, index: true, null: false
t.string :key, null: false
t.belongs_to :group, polymorphic: true, index: true
t.integer :group_owner_id, index: true
t.belongs_to :notifier, polymorphic: true, index: true
t.text :parameters
t.datetime :opened_at
t.timestamps null: false
end
target
通知する先の User
recipient って名前は使わないんだな
notifiable
通知を発生させたモノ
記事に対するコメントとか、イイネとか
key
通知種別
デフォルトでは "#{to_resource_name}.default"
"comment.create" とかを想像しやすい、いい名前
実際、notifiable の CRUD に合わせて自動で通知を送る (tracked) する場合は "#{to_resource_name}.create", "#{to_resource_name}.update" が使われる
group
通知を集約する単位
notifiable の親にすることが多いんじゃないか
コメントが付いたよ、は記事単位でまとめるだろう
イイネは個々の Tweet 単位だと大変なので、すべてをまとめるかもしれない
group_owner_id
グループ通知をまとめるときの親。(xxx 他 6 件、と表示するときの xxx)
一番最初についた comment を指す
notifier
通知を産みだした人
comment をした人
parameters
メタデータなんでも箱
opened_at
target がこの通知を確認した日時
デフォルトで用意されているもの
たぶん使わないけど眺めるだけ。
Routes/Controller
code:rb
Rails.application.routes.draw do
notify_to :users
end
code:sh
$ rails routes
code:log
Prefix Verb URI Pattern Controller#Action
open_all_user_notifications POST /users/:user_id/notifications/open_all(.:format) activity_notification/notifications#open_all {:target_type=>"users"}
move_user_notification GET /users/:user_id/notifications/:id/move(.:format) activity_notification/notifications#move {:target_type=>"users"}
open_user_notification PUT /users/:user_id/notifications/:id/open(.:format) activity_notification/notifications#open {:target_type=>"users"}
user_notifications GET /users/:user_id/notifications(.:format) activity_notification/notifications#index {:target_type=>"users"}
user_notification GET /users/:user_id/notifications/:id(.:format) activity_notification/notifications#show {:target_type=>"users"}
DELETE /users/:user_id/notifications/:id(.:format) activity_notification/notifications#destroy {:target_type=>"users"}
index
code:log
Started GET "/users/1/notifications" for ::1 at 2021-03-07 13:12:22 +0900
Processing by ActivityNotification::NotificationsController#index as HTML
Parameters: {"target_type"=>"users", "user_id"=>"1"}
User Load (2.6ms) SELECT users.* FROM users WHERE users.id = 1 LIMIT 1
Unpermitted parameters: :target_type, :user_id
ActivityNotification::Notification Exists? (2.3ms) SELECT 1 AS one FROM notifications WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.opened_at IS NULL AND notifications.group_owner_id IS NULL LIMIT 1
ActivityNotification::Notification Load (2.2ms) SELECT notifications.* FROM notifications WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.opened_at IS NULL AND notifications.group_owner_id IS NULL ORDER BY notifications.created_at DESC
ActivityNotification::Notification Exists? (2.0ms) SELECT 1 AS one FROM notifications WHERE notifications.group_owner_id = 1 LIMIT 1
CACHE ActivityNotification::Notification Load (0.0ms) SELECT notifications.* FROM notifications WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.opened_at IS NULL AND notifications.group_owner_id IS NULL ORDER BY notifications.created_at DESC
Comment Load (1.9ms) SELECT comments.* FROM comments WHERE comments.id = 1
ActivityNotification::Notification Load (2.0ms) SELECT notifications.* FROM notifications WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.opened_at IS NOT NULL AND notifications.group_owner_id IS NULL ORDER BY notifications.created_at DESC LIMIT 9
Rendering layout layouts/application.html.erb
Rendering activity_notification/notifications/default/index.html.erb within layouts/application
CACHE ActivityNotification::Notification Exists? (0.0ms) SELECT 1 AS one FROM notifications WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.opened_at IS NULL AND notifications.group_owner_id IS NULL LIMIT 1
↳ app/views/activity_notification/notifications/default/index.html.erb:6
CACHE ActivityNotification::Notification Load (0.0ms) SELECT notifications.* FROM notifications WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.opened_at IS NULL AND notifications.group_owner_id IS NULL ORDER BY notifications.created_at DESC
↳ app/views/activity_notification/notifications/default/index.html.erb:7
(1.9ms) SELECT COUNT(*) FROM notifications WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.opened_at IS NULL AND notifications.group_owner_id IS NULL
↳ app/views/activity_notification/notifications/default/index.html.erb:7
CACHE ActivityNotification::Notification Load (0.0ms) SELECT notifications.* FROM notifications WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.opened_at IS NULL AND notifications.group_owner_id IS NULL ORDER BY notifications.created_at DESC
↳ app/views/activity_notification/notifications/default/_default.html.erb:8
(2.0ms) SELECT COUNT(distinct notifications.notifier_id) AS count_distinct_notifications_notifier_id, notifications.group_owner_id AS notifications_group_owner_id, notifications.notifier_type AS notifications_notifier_type FROM notifications LEFT OUTER JOIN notifications group_owners_notifications ON group_owners_notifications.id = notifications.group_owner_id WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.group_owner_id = 1 AND (group_owners_notifications.notifier_type = notifications.notifier_type) AND NOT (group_owners_notifications.notifier_id = notifications.notifier_id) GROUP BY notifications.group_owner_id, notifications.notifier_type
↳ app/views/activity_notification/notifications/default/_default.html.erb:8
CACHE ActivityNotification::Notification Load (0.0ms) SELECT notifications.* FROM notifications WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.opened_at IS NULL AND notifications.group_owner_id IS NULL ORDER BY notifications.created_at DESC
↳ app/views/activity_notification/notifications/default/_default.html.erb:14
(2.5ms) SELECT COUNT(*) AS count_all, notifications.group_owner_id AS notifications_group_owner_id FROM notifications WHERE notifications.target_id = 1 AND notifications.target_type = 'User' AND notifications.group_owner_id = 1 GROUP BY notifications.group_owner_id
↳ app/views/activity_notification/notifications/default/_default.html.erb:14
Rendered activity_notification/notifications/default/_default.html.erb (Duration: 10.6ms | Allocations: 4479)
Rendered activity_notification/notifications/default/index.html.erb within layouts/application (Duration: 16.2ms | Allocations: 7954)
Webpacker Everything's up-to-date. Nothing to do Rendered layout layouts/application.html.erb (Duration: 21.5ms | Allocations: 11524)
Completed 200 OK in 49ms (Views: 15.6ms | ActiveRecord: 19.5ms | Allocations: 20626)
CommonController で prepend_before_action :set_target されている
User.find_by!(id: params[:user_id])
code:rb
load_index if params:reload.to_s.to_boolean(true) String#to_boolean is 何……
code:ruby
def to_boolean(default = nil)
return default
end
code:ruby
def load_index
@notifications =
when :opened, 'opened'
@target.opened_notification_index_with_attributes(@index_options)
when :unopened, 'unopened'
@target.unopened_notification_index_with_attributes(@index_options)
else
@target.notification_index_with_attributes(@index_options)
end
end
@target (= User obj) に何か生えてる
acts_as_target で増える
code:ruby
def notification_index_with_attributes(options = {})
arrange_notification_index(method(:unopened_notification_index_with_attributes),
method(:opened_notification_index_with_attributes),
options)
end
面白い
code:ruby
def _opened_notification_index(options = {})
limit = options:limit || ActivityNotification.config.opened_index_limit notifications.opened_index(limit, reverse, with_group_members).filtered_by_options(options)
end
code:ruby
scope :opened_index, ->(limit, reverse = false, with_group_members = false) {
target_index = with_group_members ? opened_only(limit) : opened_only(limit).group_owners_only
reverse ? target_index.earliest_order : target_index.latest_order
}
code:ruby
scope :latest_order, -> { order(created_at: :desc) }
code:ruby
scope :opened_only!, -> { where.not(opened_at: nil) }
scope :opened_only, ->(limit) { opened_only!.limit(limit) }
code:ruby
scope :group_owners_only, -> { where(group_owner_id: nil) }