APIモードのRailsでActive StorageとS3を使ってバックエンド、Reactでフロントエンドを実装したい
作り終わってから思い出して書き始めたので抜けがあるかもしれない
バックエンド(Rails API)
- APIモードでRailsアプリを作ってCORSの設定とかをいい感じにする
- aws-sdk-s3とactive_model_serializersをGemfileに追加してbundle install
/config/storage.ymlにS3使うためにservice、access_key_id、secret_access_key、region、bucketの指定をする qiita.com
$ rails active_storage:install
して$ rails db:migrate
するUserモデルを作る
/app/models/user.rbに
has_one_attached :avatar
を追加する qiita.com/config/environments/development.rbと/config/environments/production.rbと/config/environments/test.rbに
config.active_storage.service = :amazon
を追加(開発環境ではS3じゃなくてローカルに保存したいとかならここで変更)/app/serializers/user_serializer.rbにUserのSerializerを実装する。以下のavatarメソッドはlocalに保存する設定のときは失敗するので要注意。
#例 class UserSerializer < ActiveModel::Serializer attributes %i[id name avatar] def avatar object.avatar.attachment.service.send(:object_for, object.avatar.key).public_url if object.avatar.attached? end end
ルーティングを追加
post '/users/current/avatar', to: 'users#update_avatar
UsersControllerを作ってUserのavatarを更新するためのメソッドを作る
# 例 def update_avatar @user.avatar = params[:avatar] if @user.save render json: @user, status: :ok else render json: @user.errors.full_messages, status: :bad end end
- あとは適当に必要なAPIを作る。
フロントエンド(React+Redux)
- アップロードページの例
import React from "react"; import { useDispatch } from "react-redux"; import { upload_user_avatar } from "../../actions/user"; const MyPage = props => { const file_input = React.useRef(); const dispatch = useDispatch(); const handle_submit = e => { e.preventDefault(); if ( file_input && file_input.current && file_input.current.files && file_input.current.files.length > 0 ) { const submit_data = new FormData(); submit_data.append("avatar", file_input.current.files[0]); dispatch(upload_user_avatar(submit_data)); } } return ( <form onSubmit={handle_submit}> <input type="file" ref={file_input} accept="image/*" /> <input type="submit" /> </form> ); }
- actionの例
export const UPLOAD_USER_AVATAR_REQUEST = "UPLOAD_USER_AVATAR_REQUEST"; export const UPLOAD_USER_AVATAR_SUCCESS = "UPLOAD_USER_AVATAR_SUCCESS"; export const UPLOAD_USER_AVATAR_FAILURE = "UPLOAD_USER_AVATAR_FAILURE"; export const upload_user_avatar = submit_data => async dispatch => { dispatch({ type: UPLOAD_USER_AVATAR_REQUEST }); setAuthKeys(); try { const res = await axios.post( `${config.backend_api_url}/users/current/avatar`, submit_data, { headers: { "content-type": "multipart/form-data" } } ); return dispatch({ type: UPLOAD_USER_AVATAR_SUCCESS, res }); } catch (error) { dispatch({ type: UPLOAD_USER_AVATAR_FAILURE, error }); } };
- reducerの例
import { UPLOAD_USER_AVATAR_REQUEST, UPLOAD_USER_AVATAR_SUCCESS, UPLOAD_USER_AVATAR_FAILURE, } from "../actions/user"; const initialState = {}; export default (state = initialState, action) => { switch (action.type) { case UPLOAD_USER_AVATAR_REQUEST: return { ...state }; case UPLOAD_USER_AVATAR_SUCCESS: return { ...state, user: action.res.data.user }; case UPLOAD_USER_AVATAR_FAILURE: return { ...state, error: action.error }; default: return { ...state }; } };
devise-token-authのvalidate_tokenのレスポンスをSerializer使ってカスタマイズしたい
class User < ActiveRecord::Base # いろんな処理 def token_validation_response UserSerializer.new(self, root: false).as_json end end
Redisを使って「急上昇」を実装
やりたいこと
Redisを使って「急上昇」を実装したい
やってみたやりかた
ソート済みセット型のインスタンスとして 以下を使う
- スコアの最新の増減を記録する用:score_diff
- 過去のスコアを記録する用:score
手順
- アイテムのスコアを変動させる
diffは変動量でitem_idはアイテムのIDとする
- アイテムのスコアを変動させる
zincrby score_diff diff item_id
- 一定時間ごとにscoreを更新
その際にweightsで重みを設定することで古いスコア変動の影響を小さくする
score_(n+1) = score_diff_n + 0.7 * score_n = Σ(i, 1→n-1) (0.7 ^ (n-i)) * score_diff_i
zunionstore score 2 score score_diff weights 0.7 1.0 del score_diff
- 現在の急上昇リストを取得
zrevrange score 0 list_size
参考
Hyperappのボイラープレートを作った
概要
- Hyperapp
- Hyperapp Router
- Picostyle
- Atomic Design
を使用するボイラープレートを作った
Repository
デモ
展望
- TypeScript
- Test
- axiosを使った簡単なAPIコール
を追加したい