Rubyと筋肉とギターとわたし

筋トレが仕事です

【Ruby on Rails】rails consoleでActionDispatch::Http::Uploadedfileを作成・保存する

どうもてぃ。

久しぶりのRailsで少々ハマったのでメモをば。

やりたいこと

ローカルのファイルを読み込んで、carrierwaveに割り当てているカラムに保存したい。

ただそれだけ。

Let's go

まずはファイルの読み込み。

今回はtmp/uploads/配下にファイルを作っているものとします。

path = Dir.glob(Rails.root.join('tmp', 'uploads', 'test.pdf'))

filename = File.basename(path)
tempfile = File.open(path)

とりあえずこれで準備はおk

ActionDispatch::Http::Uploadedfileで読み込む

上記を使って読み込みます。

uploaded_file = ActionDispatch::Http::Uploadedfile.new(
  filename: filename,
  type: 'application/pdf',
  tempfile: tempfile
)

User.first.update(file: uploaded_file)

UserモデルにUserFileUploaderをマウントしたfileカラムがあるものとして保存してます。

class User < ApplicationRecord
・
・
・
  # ↓こういうやつ
  mount_uploaders :file, UserFileUploader 
・
・
・
end

終わり

ファイルタイプによってtypeをimage/jpegとかに変えれば画像もちゃんと保存できます。

定期的にRailsに触っておかないと忘れちゃいますね。

【Golang】sql-migrate upがCannot parse dateと出てうまくいかないとき

f:id:rdwbocungelt5:20200720165613p:plain

どうもてぃ。

今日もGolangやっていきます。

マイグレーションを実行してみた

% docker-compose exec golang sql-migrate up -config=config/dbconfig.yml
Migration failed: Cannot parse dates.

Make sure that the parseTime option is supplied to your database connection.
Check https://github.com/go-sql-driver/mysql#parsetime for more info.

上記のようにエラーになってしまいます。

sql-migrate newはうまくいってますが、upがうまくいかない。コード上でparseTimeとかつかっているところなんてない。

ただ、エラーメッセージを見てみると、

Make sure that the parseTime option is supplied to your database connection.

これですよ。

parseTimeオプションを追加しろと。

github.com

parseTimeはbool型のようなので、dbconfig.ymlの接続設定にparseTime=trueを追加しました。

development:
  dialect: mysql
  dir: pkg/db/migrations
  datasource: root:password@tcp(db:3306)/database_name?parseTime=true

これでマイグレーションが通るようになりましたとさ。

【Rails】enumからi18nを適応したhashを取得したいとき

どうもてぃです。

enumについてあんまり理解してなかったのでメモとして残します。

gemのenum_helpを入れている状態です。

環境

% lsb_release -a
No LSB modules are available.
Distributor ID: LinuxMint
Description:    Linux Mint 19.3 Tricia
Release:    19.3
Codename:   tricia

TL; DR

User model

class User < ApplicationRecord
  enum role: { admin: 0, manage: 1, guest: 2 }


・
・
・
end

locales/js.yml

ja:
  enums:
    user:
      role:
        admin: 管理者
        manage: マネージャー
        guest: 一般
・
・
・

コンソール

pry(main)> User.roles
=> {"admin" => 0, "manage" => 1, "guest" => 2}

pry(main)> User.roles_i18n
=> {"admin" => "管理者", "manage" => "マネージャー", "guest" => "一般"}

終わり

Userのインスタンスから取得することもできますが、今回はインスタンスを生成せずにenumを使いたかったので、i18nを試してみたらいけました。

User.roles_i18n.invertとすればselect_boxとかに使えそうですね。

【Golang】超絶簡素なwebサーバーを作る その3

f:id:rdwbocungelt5:20200720165613p:plain

どうもてぃ。

前回の記事がこちら。

smot93516.hatenablog.jp

template.htmlを読み込んでstructを渡す形のコードを前回作成しました。

まずはそのコードから見ていきます。

tempalteにstructを渡す

type Article struct {
    Title string
    Body  template.HTML
}

var tpl *template.Template

func init() {
    tpl = template.Must(template.ParseFiles("template.html"))
}

func escapeHTML(html string) (tpl template.HTML) {
    tpl = template.HTML(html)

    return
}

func helloHandler(rw http.ResponseWriter, req *http.Request) {
    article := Article{
        Title: "golang practice",
        Body:  escapeHTML("<h1>hello golang</h1>"),
    }

    if err := tpl.ExecuteTemplate(rw, "template.html", article); err != nil {
        log.Fatalln(err.Error())
    }
}

該当箇所だけ取り上げました。

Articleのstructを定義し、helloHandlerで初期化してます。

func helloHandler(rw http.ResponseWriter, req *http.Request) {
    // ここね
    article := Article{
        Title: "golang practice",
        Body:  escapeHTML("<h1>hello golang</h1>"),
    }、

    if err := tpl.ExecuteTemplate(rw, "template.html", article); err != nil {
        log.Fatalln(err.Error())
    }
}

そして、単にBodyを文字列としてtemplateにわたした際、htmlタグをエスケープしてくれないのでescapeHTML関数を作成し、template.HTMLに変換する処理を入れてます(わざわざ別関数にしなくても良い気がしてきた…)

article := Article{
    Title: "golang practice",
    Body: template.HTML("<h1>hello golang</h1>"),
}

これでいいかも。

だた、この方法はXSSの対象となるやりかたなので、使用する際は十分注意するようにしてください。

エスケープせずうまく出力する方法は以下。

type Article struct {
    Title string
    Body  string
}

var tpl *template.Template

func safeHTMLTemplate(text string) template.HTML {
    return template.HTML(text)
}

func init() {
    funcMap := template.FuncMap{ "safehtml": safeHTMLTemplate }
    tpl = template.Must(template.New("").Funcs(funcMap).ParseFiles("template.html"))
}

func helloHandler(rw http.ResponseWriter, req *http.Request) {
    article := Article{
        Title: "golang practice",
        Body:  "<h1>hello golang</h1>",
    }

    if err := tpl.ExecuteTemplate(rw, "template.html", article); err != nil {
        log.Fatalln(err.Error())
    }
}

Bodyをstringにしてます。 templateのエスケープしない処理部分をinit()に逃がしました。

template側は

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>{{.Title}}</title>
</head>
<body>
  {{.Body|safehtml}}
</body>
</html>

.Body|safehtmlとすることでhtmlタグのエスケープを気にせず、うまいこと表示を実現してます。

この方法は以下の記事を参考にしました。感謝。

qiita.com

特にセキュリティ意識しなければどちらでも問題ないとは思います。

最初の方がイメージしやすいので、慣れたらtemplate.FuncMap等使ってみると良いかもです。

jsonを返してみる

バックエンドはGo、フロントエンドはReactやVueを使いたい、そんなときGoはjsonを返すのがメインになると思います。

まずは超かんたんにhandlerを作成してみます。

func jsonResponseHandler(rw http.ResponseWriter, req *http.Request) {
    data := map[string]interface{}{
        "message": "hello golang",
        "status":  http.StatusOK,
    }

    // map to json
    bytes, err := json.Marshal(data)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Fprint(rw, string(bytes))
}

func main() {
    // http.Handle("/hello", http.HandlerFunc(helloHandler))
    http.HandleFunc("/hello", helloHandler)
    http.HandleFunc("/dog", dogHandler)
    http.HandleFunc("/json", jsonResponseHandler)

    http.ListenAndServe(":3000", nil)
}

該当箇所をピックアップしてます。requestは特に気にせず、決まったjsonを返す形です。

手順としては…

  • map作成(valueはinterface型が望ましい)
  • mapをjsonに変換
  • 取得したbytesを文字列に変換して表示

です。json.Marshal()が今回の味噌。

func Marshal(v interface{}) ([]byte, error) {
    e := newEncodeState()

    err := e.marshal(v, encOpts{escapeHTML: true})
    if err != nil {
        return nil, err
    }
    buf := append([]byte(nil), e.Bytes()...)

    encodeStatePool.Put(e)

    return buf, nil
}

godocを見てみると、Marshalに渡すものは何でもokと(interface型はtypescriptで言うanyみたいなもん)。

深く見る必要はないので、返り値を確認するとbyteとerrorが返ってきてます。

何が返ってくるかはgodocを確認するか、エディタの拡張機能で定義元を確認するようにしてください。自分はvim-goを使ってます。

json.Marshal()を使うだけでjsonをbyte型に変換したものが得られるので、文字列に変換してやると見事にjsonを返すエンドポイントの完成ですb

structを使ったjsonを返してみる

次はmapではなくstructを使って返してみます。

DBからデータを抽出してstructにキャッシュし、それをjsonに変換してレスポンスを返す…みたいなことを想定してます。

type Product struct {
    Name     string `json:"name"`
    Price    int    `json:"price"`
    Quantity int    `json:"quantity"`
}

func productResponseHandler(rw http.ResponseWriter, req *http.Request) {
    // product := Product{"商品A", 100, 10}
    product := Product{
        Name:     "商品A",
        Price:    100,
        Quantity: 10,
    }

    // struct to json byte
    // bytes, err := json.Marshal(product)でもおk
    bytes, err := json.Marshal(&product)
    if err != nil {
        http.Error(rw, err.Error(), http.StatusInternalServerError)
        return
    }

    rw.Header().Set("Content-Type", "application/json")
    fmt.Fprint(rw, string(bytes))
}

また、追加部分だけ抜き出してます。

こう見ると、ほとんどmapのときと同じです。json.Marshal()をしてやるだけ。structの場合ポインタを渡すことが多いので、&productでわたしてます。

他にheader情報を付与したりhttp.Error()で明示的にエラーが出るようにしてますが、mapのときと同じ構成でも問題なく動きます。

ポイントはstructの定義の際に、json指定をしてあげること。

type Product struct {
    Name     string `json:"name"`
    Price    int    `json:"price"`
    Quantity int    `json:"quantity"`
}

これを指定しないと何が返ってくるかというと

f:id:rdwbocungelt5:20200915173004p:plain

ちゃんとjsonになってくれません。

structでレスポンスを返す場合は必ずjson指定をしましょう。

おわり

全体を通したコードがこちら

package main

import (
    "encoding/json"
    "fmt"
    "html/template"
    "log"
    "net/http"
)

type Article struct {
    Title string
    Body  string
}

type Product struct {
    Name     string `json:"name"`
    Price    int    `json:"price"`
    Quantity int    `json:"quantity"`
}

var tpl *template.Template

func safeHTMLTemplate(text string) template.HTML {
    return template.HTML(text)
}

func init() {
    funcMap := template.FuncMap{
        "safehtml": safeHTMLTemplate,
    }
    tpl = template.Must(template.New("").Funcs(funcMap).ParseFiles("template.html"))
}

func helloHandler(rw http.ResponseWriter, req *http.Request) {
    article := Article{
        Title: "golang practice",
        Body:  "<h1>hello golang</h1>",
    }

    if err := tpl.ExecuteTemplate(rw, "template.html", article); err != nil {
        log.Fatalln(err.Error())
    }
}

func dogHandler(rw http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(rw, "<h1>dogs</h1><h2>dog dog dog</h2>")
}

func jsonResponseHandler(rw http.ResponseWriter, req *http.Request) {
    data := map[string]interface{}{
        "message": "hello golang",
        "status":  http.StatusOK,
    }

    // map to json
    bytes, err := json.Marshal(data)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Fprint(rw, string(bytes))
}

func productResponseHandler(rw http.ResponseWriter, req *http.Request) {
    // product := Product{"商品A", 100, 10}
    product := Product{
        Name:     "商品A",
        Price:    100,
        Quantity: 10,
    }

    // struct to json byte
    bytes, err := json.Marshal(product)
    if err != nil {
        http.Error(rw, err.Error(), http.StatusInternalServerError)
        return
    }

    rw.Header().Set("Content-Type", "application/json")
    fmt.Fprint(rw, string(bytes))
}

func main() {
    // http.Handle("/hello", http.HandlerFunc返す(helloHandler))
    http.HandleFunc("/hello", helloHandler)
    http.HandleFunc("/dog", dogHandler)
    http.HandleFunc("/json", jsonResponseHandler)
    http.HandleFunc("/product", productResponseHandler)

    http.ListenAndServe(":3000", nil)
}

次はもう少しhttp.Requestに関してまとめて、フレームワークを使った場合どうなるか、というのもまとめていければと思います。

ではでは。

【Docker】ローカルにredisをインストールしたくないときの対処法

どうもてぃです。

railsの完全ローカル環境を構築しないといけなくなったんですが、いかんせんインストールするものが多い。

題名の通りredisをインストールするのは良いんですが、他のプロジェクトでdockerを使っててdocker-compose upした際にポートがぶつかるのが面倒くさい。

そういった面倒臭さやインストールする煩わしさ、インストール後の設定の憂鬱さを解決する方法を伝授します。

環境

% lsb_release -a
No LSB modules are available.
Distributor ID: LinuxMint
Description:    Linux Mint 19.3 Tricia
Release:    19.3
Codename:   tricia
  • Docker version 19.03.6, build 369ce74a3c

TL; DR

$ docker pull redis

# requirepassはrails側で設定しているもの
$ docker run --name redis-container -d -p 6379:6379 redis redis-server --appendonly yes --requirepass password

データを永続化させたいとき

今回はただの開発環境でrailssidekiqのために必要であったため、特に永続化は気にしてません。

ですが、永続化させたくてどうしようもない方のために…

リファレンスを乗せときます\(^o^)/

docs.docker.jp

dockerのリファレンスは神なのでぜひ見ましょうね…





冗談です。

以下のコマンドでたぶんいけます。

$ docker run --name redis-container -d -p 6379:6379 -v redis-data:/data redis redis-server --appendonly yes --requirepass password

おわりに

docker run オプションについては自分の過去記事を漁ってもらうか、安心と信頼のとても見やすいリファレンスを確認するとよいです。

redis以外にも入れたくないものがあればdockerで同じように対応してみるとdockerに慣れていけるんじゃないでしょうかね。

最後にdockerリファレンスのリンク貼っときますね。

docs.docker.jp

【PostgreSQL】rails db:create時にrole <name> does not existsが出たときの備忘録

f:id:rdwbocungelt5:20181010100712p:plain

どうもてぃ。

久々にローカルDB環境を作ってて10分ほどハマったので、二度とないように備忘録書いときます。

環境

% cat /etc/lsb-release 
DISTRIB_ID=LinuxMint
DISTRIB_RELEASE=19.3
DISTRIB_CODENAME=tricia
DISTRIB_DESCRIPTION="Linux Mint 19.3 Tricia"

database.ymlの設定

config/database.ymlはこんな感じ。

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: <%= ENV['POSTGRES_DB'] %>

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: <%= ENV['POSTGRES_DB_TEST'] %>

usernameやpasswordは面倒くさかったので設定してません。

Let's rails db:create

% bundle exec rails db:create
% bundle exec rails db:create
FATAL:  role "motty" does not exist
Couldn't create 'project_dev' database. Please check your configuration.
rails aborted!
ActiveRecord::NoDatabaseError: FATAL:  role "motty" does not exist
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/postgresql_adapter.rb:696:in `rescue in connect'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/postgresql_adapter.rb:691:in `connect'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/postgresql_adapter.rb:223:in `initialize'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/postgresql_adapter.rb:48:in `new'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/postgresql_adapter.rb:48:in `postgresql_connection'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:830:in `new_connection'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:874:in `checkout_new_connection'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:853:in `try_to_checkout_new_connection'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:814:in `acquire_connection'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:538:in `checkout'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:382:in `connection'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_adapters/abstract/connection_pool.rb:1033:in `retrieve_connection'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_handling.rb:118:in `retrieve_connection'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/connection_handling.rb:90:in `connection'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/tasks/postgresql_database_tasks.rb:12:in `connection'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/tasks/postgresql_database_tasks.rb:21:in `create'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/tasks/database_tasks.rb:119:in `create'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/tasks/database_tasks.rb:139:in `block in create_current'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/tasks/database_tasks.rb:316:in `block in each_current_configuration'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/tasks/database_tasks.rb:313:in `each'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/tasks/database_tasks.rb:313:in `each_current_configuration'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/tasks/database_tasks.rb:138:in `create_current'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.4.3/lib/active_record/railties/databases.rake:29:in `block (2 levels) in <main>'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/railties-5.2.4.3/lib/rails/commands/rake/rake_command.rb:23:in `block in perform'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/railties-5.2.4.3/lib/rails/commands/rake/rake_command.rb:20:in `perform'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/railties-5.2.4.3/lib/rails/command.rb:48:in `invoke'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/railties-5.2.4.3/lib/rails/commands.rb:18:in `<main>'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require_with_bootsnap_lfi'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:31:in `require'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `block in require'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:257:in `load_dependency'
/home/motty/github.com/project/vendor/bundle/ruby/2.6.0/gems/activesupport-5.2.4.3/lib/active_support/dependencies.rb:291:in `require'
bin/rails:4:in `<main>'

postgresqlをインストールした後なにもしてなかったので、ロールがないって怒られちゃいました。

roleを作成する

まずはポスグレの中に入ります

% sudo -u postgres psql
psql (10.14 (Ubuntu 10.14-0ubuntu0.18.04.1))
Type "help" for help.

postgres=# 

ロールの確認をすると

postgres=# \du
                                   List of roles
 Role name |                         Attributes                         | Member of 
-----------+------------------------------------------------------------+-----------
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}

mottyっていうロールないですね(そりゃそうだわ)

てなわけで作りま。

postgres=# create role motty login createdb createrole password 'password';
CREATE ROLE

postgres=# \du
                                   List of roles
 Role name |                         Attributes                         | Member of 
-----------+------------------------------------------------------------+-----------
 motty     | Create role, Create DB                                     | {}
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}

完璧にロールmottyが作成できました。

\qで戻れます。

おわり

rails db:createも無事に通りました。

普段自分が作ったdocker templateを使用してるので、まさかこんなところでハマるとは思ってませんでした。

もう二度とローカル環境構築とかいう無駄なことはやりたくないなぁ、と思う今日この頃でした。

おしまい。

【備忘録】asdf install rubyが出来ない時の対処法

どうもてぃ。

バージョン管理でasdfを使っているのですが、最新バージョンの言語をとってこようとした際にエラーになったので対処法を書いときます。

TL; DR

$ asdf plugin-update ruby

$ asdf install ruby 2.6.6

対処法はissueを探す

同じようなこと起きてる人たくさんいました。

$ asdf install ruby 2.6.6
ruby-build: definition not found:2.6.6

単にruby 2.6.6がインストールの候補に入ってないみたいでしたね。

github.com

終わり

他の言語で同じ現象がおきたときの対処策として備忘録書いときました。

なるべくわすれないように。