20200324のRubyに関する記事は20件です。

Railsフリマアプリ payjpを使ってクレジットカードを登録・削除

payjp とは・・・?

クレジットカードを登録、変更、購入を行ってくれる便利なAPIです。
みんさんもフリマアプリ、ECサイトでクレジットカードを使って商品の購入すると思います。その時登録したクレジットカード情報はどこに保存されているでしょうか?私は運営するサイトで暗号化されて管理されているのだと思っていましたが、違うようです。たしかにサイトのデータベースにクレジットカード情報を登録するのはセキュリティ上よろしくないですよね。
このとき登場するのがpayjpになります。

payjpの仕組み

まずpayjpとフリマアプリではどのような流れでクレジットカードの処理が行われているのかざっくりとイメージしましょう!
私はここを飛ばしてすぐコードを書き初めてため概念の理解に時間がかかりました。わかれば非常に単純でした。笑
以下の記事がとてもわかりやすく参考になります。
Pay.jpの仕組みについて!
セキュリティに加えて、payjpで発行されるトークンと顧客IDが紐つけられ永続化することで、ユーザーがスムーズに決済できることがポイントかと思います。

実装内容

参考記事

実装においては以下の記事を参考としました。事情にわかりやすく説明されていています。
Payjpでクレジットカード登録と削除機能を実装する(Rails)

基本的な実装な上記記事を見れば理解できると思います。今回は実装中につまずいたポイントや復習した内容についてまとめます。

JavaScript(jQuery)を使ってトークンを発行

ここまでの学習でJSについて理解が足りていなかったので、カリキュラムを復習、新しい知識をインプットしながらトークンの発行の仕方を理解しました。payjpに関して記事は結構あるのですが、コードがボーンって書いてあるだけだったので詳しく見ていきましょう!!

payjp.js

document.addEventListener(
  "DOMContentLoaded", e => {
    Payjp.setPublicKey("自分の公開鍵を記述する");
    var btn = document.getElementById('token_submit');
    btn.addEventListener("click", (e) => {
      e.preventDefault();

      var card = {
        number: $("#number").val(),
        cvc: $("#cvc").val(),
        exp_month: $("#exp_month").val(),
        exp_year: $("#exp_year").val()
      }; 


      Payjp.createToken(card, (status, response) => {
        if (status === 200) {
          $("#number").removeAttr("name");
          $("#cvc").removeAttr("name");
          $("#exp_month").removeAttr("name");
          $("#exp_year").removeAttr("name"); 
          $("#card_token").append(
            $('<input type="hidden" name="payjp-token">').val(response.id)
          );
          document.inputForm.submit();
          alert("登録が完了しました"); 
        } else {
          alert("カード情報が正しくありません。"); 
        }
      });
    });
  },
  false
);

まずpayjpとに入力データを渡すために、公開鍵を記述します。
秘密鍵と公開鍵がありますので間違わないように注意しましょう。秘密鍵はcontrollerに記述します。

Payjp.setPublicKey("自分の公開鍵を記述する");

viewでカード情報を入力して、登録ボタン(#token_submit)をクリックした時、データを送信するデフォルトの機能を停止し、

var btn = document.getElementById('token_submit');
    btn.addEventListener("click", (e) => {
      e.preventDefault();

クレジットカード情報の、number,cvc,exp_month,exp_yearを.val()で属性の値を取得します。

var card = {
        number: $("#number").val(),
        cvc: $("#cvc").val(),
        exp_month: $("#exp_month").val(),
        exp_year: $("#exp_year").val()
      }; 

上記で取得した値はPayjp.createToken()の引数に渡し管理されます。
status === 200はリクエストが成功している状況です。
ここでカード情報はサーバーには保存しないため、removeAttrによって属性を削除します。そしてresponse.idを取得しトークンをデータベースに送るため隠しタグを生成します。

Payjp.createToken(card, (status, response) => {
        if (status === 200) {
          $("#number").removeAttr("name");
          $("#cvc").removeAttr("name");
          $("#exp_month").removeAttr("name");
          $("#exp_year").removeAttr("name"); 
          $("#card_token").append(
            $('<input type="hidden" name="payjp-token">').val(response.id)
          );
          document.inputForm.submit();
          alert("登録が完了しました"); 

最後に!

便利なAPIを使うと簡単に便利な機能を実装することができますが(結構時間かかったけど)、何でこの記述をしているのか考えるのもいい勉強になりました。特にJSがあまり理解できていなかったので、この機会復習もできました。
理解が間違っていましたらぜひ教えてください。
読んでくださりありがとうございました!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kinx 実現技術 - JIT

JIT Compile - Just In Time コンパイル

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。作ったものの紹介だけではなく実現のために使った技術を紹介していくのも貢献。その道の人には当たり前でも、そうでない人にも興味をもって貰えるかもしれない。

前回のテーマは VM (Virtual Machine)。今回のテーマは JIT。

正直、JIT は道半ばでコード晒すのもイマイチだが、参考にはなるかと思う。コントリビューター募集中というのもあるので興味があればよろしくお願いします。

JIT コンパイラとは

Just In Time、つまり まさにその場で コンパイルして実行する方式。ネイティブコード(機械語)にコンパイルして実行するため実行速度を追い求める人たちの間では欲しくてたまらない機能の一つ。

ただし、プロセッサーごとに個別に実装しないといけない。x64 だけ、とかならまだいいんだけど、どっかで ARM くらいはサポートすっかなー、とか、PowerPC は、SPARC?、とかになると、そもそもテストするのが大変だ。

Ruby 方式

Ruby も最近 JIT サポートをアピールしはじめたが、その辺の大変さからうまく?逃げている。つまり、C ソースを作成して、他のコンパイラでコンパイル したものを自分のプロセスに取り込む、といったことをしている。コンパイラがホストコンピュータ用のコードを出力してくれるし、もともと他人のモノなので Ruby 自体はプロセッサーを意識しなくて良い。

まあ逃げ方としてはそうなるとは思うが、しかしそれでは実行環境にコンパイラが必要になってしまう。しかもなにより Windows 環境で Visual Studio をインストールしてください、とか、Cygwin インストールしてください、とは 言えない というのが私の感覚です。開発者以外にコンパイラをインストールさせるのは ... かなり気が引ける。

sljit

そこで sljit を採用した。sljit はアセンブラを抽象化しており、その抽象化アセンブラで書くことでコード出力時に x64 や ARM などに合わせて出力ができるようになっている。その代わり、レジスタ数とかどのプロセッサーでも共通に使えるようにミニマムに合わせておかなければならない。

ただし、x64 以外ではテストしていないので悪しからず。

尚、ネイティブ・コード用 IR 出力ルーチンは src/ast_native.c にあり、sljit を使ったネイティブ・コード用コンパイラは src/nir_compile.c にある。そして、まだ実装されていないものが沢山あることがわかる。。。

個別の実装(特徴)

いくつか個別の実装の内容を記載してみる。まだまだやらなきゃいけないことが沢山あるなあ。

IR (Intermediate Representation)

ネイティブ・コードに変換する上で扱いやすいように、VM 用の IR と異なるコードを出力する。いやいや本当に違うものを出力する。なぜなら、例外の扱いとかコンパイル時点における型チェックの厳密さとかが全く異なるからで、それに合わせて違う体系になっている。

例えば;

native nfib(n) {
    if (n < 3) return n;
    return nfib(n-2) + nfib(n-1);
}

function fib(n) {
    if (n < 3) return n;
    return fib(n-2) + fib(n-1);
}

これは以下のようになる。

nfib(1, 10):
  .L0
       1:   jmp                     .L1
  .L1
       2:   load                    r1, $(0,0)
       3:   lt                      r2 = r1, 3
       4:   jz                      .L4
  .L2
       5:   load                    r3, $(0,0)
       6:   ret                     r3
  .L3
       8:   jmp                     .L4
  .L4
       9:   load                    r4, $(0,0)
       a:   sub                     r5 = r4, 2
       b:   arg                     r5
       c:   call                    r6 = [rec-call]()
       d:   excpt                   r7 = check
       e:   jz                      .L6
  .L5
       f:   excpt                   off
      10:   excpt                   on
      11:   ret                     0
  .L6
      13:   load                    r7, $(0,0)
      14:   sub                     r8 = r7, 1
      15:   arg                     r8
      16:   call                    r9 = [rec-call]()
      17:   excpt                   r10 = check
      18:   jz                      .L8
  .L7
      19:   excpt                   off
      1a:   excpt                   on
      1b:   ret                     0
  .L8
      1d:   add                     r10 = r6, r9
      1e:   ret                     r10
  .L9
      20:   ret                     0

fib:
  .L460
     d06:   enter                   23, vars(1), args(1)
  .L461
     d07:   lt_v0i                  $0(0), 3
     d08:   jz                      .L463(d0a)
  .L462
     d09:   retvl0                  $0(0)
  .L463
     d0a:   pushvl0                 $0(0)
     d0b:   subi                    2
     d0c:   callvl1                 $1(34), 1
     d0d:   pushvl0                 $0(0)
     d0e:   subi                    1
     d0f:   callvl1                 $1(34), 1
     d10:   add
     d11:   ret
     d12:   halt

長くなるので各命令の意味は省略します。ちょっと無駄なコードも見え隠れ(隠れてない)するが、こんな感じの出力を現時点ではする。しかし、こうやって書くと改善点も客観的に見えるというメリットもあるね。

例外処理

ネイティブコードでも try-catch-finally をサポートするにあたり、多少工夫が必要。setjmp/longjmp が使えれば簡単だが、JIT コード内で、しかも複数プロセッサー対応を目指した抽象化アセンブラの範囲内では使えないので実現するのはちょっと骨が折れた。

具体的には以下の通りに実装されている。

  • 例外が発生したかをチェックできる特別なフラグを用意する。
  • 例外発生時、フラグを立て、キャッチ・ルーチンがあればそこにジャンプする。
  • finally があればそれを実行する。
  • 同じ関数内に次の(外側の)キャッチ・ルーチンがあれば、そこにジャンプ。
  • 無ければ return する。
  • リターンは関数呼び出しの次のコードなので、そこには例外が発生したかのフラグを確認するコードを出力しておく。
  • 例外発生を検知したら、上記と同じ形で伝播させていく。

スタックトレースも作れるはずなのだが、他のことを優先しているので今はできていない。どっかできちんとやる。

型チェック

ネイティブコードでは型チェックは重要。関数の入口で引数の型をチェック。レキシカル変数をアクセスする場合もチェック。関数から帰ってきたときにもチェック。型が期待するものと違ってたらすぐさま例外を上げるようになっている。

Switch-Case

sljit のライブラリを色々見たのだが、sljit の世界の中でどうしてもジャンプテーブルの実現方法が分からなかったので、今はサポートできていない(具体的にはレジスタ値にジャンプする方法がインターフェースとして用意されていない模様)。

最悪は if-else で実現するくらいだが、ジャンプテーブルの無い Switch-Case は軽自動車並みのエンジンで見た目だけ F1 カーといった感じなので、やる意味があるかどうか。sljit を改造するという手もあるが、x64 以外はすぐに対応できそうもない、といったところ。

VM 関数呼び出し

これも現在は未サポート。子 VM を起動してジャンプすれば良いとは思うが、入れ子になった VM 内で例外が発生した場合の処理が大変そうなので、まだ手がつけられていない。おそらく以下のような感じでできるのでは、と思っているが、どうだろう。

  • フレームに native からのコールであるフラグを立てる。
  • return 時、native からであれば復帰値をセットして VM から return する。
  • 例外発生時、スタックを巻き戻している最中に native コールされたフラグを見つけた場合、そこでスタックトレース情報の作成を中断し、native 向けの例外情報を設定して native にリターン。native 側に帰ってきたら、native での例外伝搬の仕組みで伝搬させていく。

他に気にするところはあるかな。例外のスタックトレースを native 関数内でも保存するようにしないといけないな。やってみる時間があれば挑戦してみる、という予定。

オブジェクトへのアクセス

レキシカル変数へのアクセスはできるので、できるはずだがこれもまだ未サポート。演算で認められていない型同士の演算をきちんと拒絶、もしくはきちんとキャストするようにした上で、オブジェクトへのインデックス・アクセスをできるようにすれば良いだけだが、先にテストコードとドキュメントとライブラリを充実させたいもので。

そのうちやる。

レジスタ割り付け

複数のプロセッサーに対応させようとすると、レジスタ数の縛りが結構キツイ。なので割り切ってレジスタ割り付けは省略。全部ストア・ロードで対応。出力コードは汚いが、マイクロベンチマークである程度パフォーマンスが出ているので、良しとした。

尚、最初の例ではあえて載せていなかったが、x64 向けコードを出力させてみると以下のようになる。うむ、汚い。大半は型チェックと例外チェックなのだが、演算内容に対しても全くレジスタ割り付けしていないのも分かるだろう。毎回ストア・ロードしているし、無駄にストアしたのを再度ロードしたりしているのでその辺は簡単に改善できそうではある。今のところ重要度は高くないのでペンディング中。return くらいは一か所に集めるか。

nfib: (native-base:0x7f1b2aa50010)
       0:   53                                push    rbx
       1:   41 57                             push    r15
       3:   41 56                             push    r14
       5:   41 55                             push    r13
       7:   55                                push    rbp
       8:   41 54                             push    r12
       a:   48 8b df                          mov     rbx, rdi
       d:   4c 8b fe                          mov     r15, rsi
      10:   4c 8b f2                          mov     r14, rdx
      13:   48 81 ec 68 02 00 00              sub     rsp, 0x268
      1a:   49 8b 47 08                       mov     rax, qword [r15+0x00000008]
      1e:   48 83 c0 01                       add     rax, 0x01
      22:   49 89 47 08                       mov     qword [r15+0x00000008], rax
      26:   48 3d 00 04 00 00                 cmp     rax, 0x400
      2c:   72 29                             jb      0x00000057
      2e:   48 c7 43 20 01 00 00 00           mov     qword [rbx+0x00000020], 0x01
      36:   48 c7 43 28 06 00 00 00           mov     qword [rbx+0x00000028], 0x06
      3e:   48 c7 c0 00 00 00 00              mov     rax, 0x00
      45:   48 81 c4 68 02 00 00              add     rsp, 0x268
      4c:   41 5c                             pop     r12
      4e:   5d                                pop     rbp
      4f:   41 5d                             pop     r13
      51:   41 5e                             pop     r14
      53:   41 5f                             pop     r15
      55:   5b                                pop     rbx
      56:   c3                                ret
      57:   49 83 bf 10 01 00 00 01           cmp     qword [r15+0x00000110], 0x01
      5f:   0f 85 52 02 00 00                 jnz     0x000002b7
      65:   49 8b 57 10                       mov     rdx, qword [r15+0x00000010]
      69:   48 89 14 24                       mov     qword [rsp], rdx
      6d:   eb 00                             jmp     0x0000006f
      6f:   48 8b 14 24                       mov     rdx, qword [rsp]
      73:   48 89 94 24 18 02 00 00           mov     qword [rsp+0x00000218], rdx
      7b:   48 c7 84 24 20 02 00 00 01 00     mov     qword [rsp+0x00000220], 0x01
      87:   00 00 48 83 bc 24 18 02 00        cmp     qword [rsp+0x00000218], 0x03
      90:   00 03                             jb      0x0000009e
      92:   72 0c 48 c7 84 24 20 02 00 00     mov     qword [rsp+0x00000220], 0x00
      9e:   00 00 00 00 48 83 bc 24 20        cmp     qword [rsp+0x00000220], 0x00
      a7:   02 00                             jz      0x000000d3
      a9:   00 00 74 2a                       mov     rdx, qword [rsp]
      ad:   48 8b 14 24 48 89 94 24           mov     qword [rsp+0x00000228], rdx
      b5:   28 02 00 00 48 8b 84 24           mov     rax, qword [rsp+0x00000228]
      bd:   28 02 00 00 48 81 c4              add     rsp, 0x268
      c4:   68 02                             pop     r12
      c6:   00                                pop     rbp
      c7:   00 41                             pop     r13
      c9:   5c 5d                             pop     r14
      cb:   41 5d                             pop     r15
      cd:   41                                pop     rbx
      ce:   5e                                ret
      cf:   41 5f                             jmp     0x0000006d
      d1:   5b c3                             jmp     0x000000d3
      d3:   eb 9c eb 00                       mov     rdx, qword [rsp]
      d7:   48 8b 14 24 48 89 94 24           mov     qword [rsp+0x00000230], rdx
      df:   30 02 00 00 48 8b 94 24           mov     rdx, qword [rsp+0x00000230]
      e7:   30 02 00 00                       sub     rdx, 0x02
      eb:   48 83 ea 02 48 89 94 24           mov     qword [rsp+0x00000238], rdx
      f3:   38 02 00 00 48 8b 94 24           mov     rdx, qword [rsp+0x00000238]
      fb:   38 02 00 00 48                    mov     qword [rsp+0x00000018], rdx
     100:   89 54 24 18 48 c7 84 24 18 01     mov     qword [rsp+0x00000118], 0x01
     10c:   00 00 01 00                       mov     rcx, qword [rbx+0x00000018]
     110:   00 00 48                          mov     rax, rbx
     113:   8b 4b 18 48                       mov     rdx, qword [r15+0x00000008]
     117:   89 d8 49 8b 57                    mov     qword [rsp+0x00000010], rdx
     11c:   08 48 89 54 24                    lea     rsi, [rsp+0x00000008]
     121:   10 48 8d                          mov     rdi, rax
     124:   74 24                             call    rcx
     126:   08 48 89 c7 ff d1 48 89           mov     qword [rsp+0x00000240], rax
     12e:   84 24 40 02 00 00 48 c7 84 24     mov     qword [rsp+0x00000248], 0x01
     13a:   48 02 00 00                       mov     rax, qword [rbx+0x00000020]
     13e:   01 00 00 00                       cmp     rax, 0x00
     142:   48 8b                             jnz     0x00000150
     144:   43 20 48 83 f8 00 75 0c 48 c7     mov     qword [rsp+0x00000248], 0x00
     150:   84 24 48 02 00 00 00 00 00        cmp     qword [rsp+0x00000248], 0x00
     159:   00 48 83 bc 24 48                 jz      0x0000018e
     15f:   02 00 00 00 0f 84 2f 00           mov     qword [rbx+0x00000020], 0x00
     167:   00 00 48 c7 43 20 00 00           mov     qword [rbx+0x00000020], 0x01
     16f:   00 00 48 c7 43 20 01 00           mov     rax, qword [rsp+0x00000210]
     177:   00 00 48 8b 84 24 10              add     rsp, 0x268
     17e:   02 00                             pop     r12
     180:   00                                pop     rbp
     181:   48 81                             pop     r13
     183:   c4 68                             pop     r14
     185:   02 00                             pop     r15
     187:   00                                pop     rbx
     188:   41                                ret
     189:   5c 5d 41 5d 41                    jmp     0x0000006d
     18e:   5e 41 5f 5b                       mov     rdx, qword [rsp]
     192:   c3 e9 df fe ff ff 48 8b           mov     qword [rsp+0x00000248], rdx
     19a:   14 24 48 89 94 24 48 02           mov     rdx, qword [rsp+0x00000248]
     1a2:   00 00 48 8b                       sub     rdx, 0x01
     1a6:   94 24 48 02 00 00 48 83           mov     qword [rsp+0x00000250], rdx
     1ae:   ea 01 48 89 94 24 50 02           mov     rdx, qword [rsp+0x00000250]
     1b6:   00 00 48 8b 94                    mov     qword [rsp+0x00000018], rdx
     1bb:   24 50 02 00 00 48 89 54 24 18     mov     qword [rsp+0x00000118], 0x01
     1c7:   48 c7 84 24                       mov     rcx, qword [rbx+0x00000018]
     1cb:   18 01 00                          mov     rax, rbx
     1ce:   00 01 00 00                       mov     rdx, qword [r15+0x00000008]
     1d2:   00 48 8b 4b 18                    mov     qword [rsp+0x00000010], rdx
     1d7:   48 89 d8 49 8b                    lea     rsi, [rsp+0x00000008]
     1dc:   57 08 48                          mov     rdi, rax
     1df:   89 54                             call    rcx
     1e1:   24 10 48 8d 74 24 08 48           mov     qword [rsp+0x00000258], rax
     1e9:   89 c7 ff d1 48 89 84 24 58 02     mov     qword [rsp+0x00000260], 0x01
     1f5:   00 00 48 c7                       mov     rax, qword [rbx+0x00000020]
     1f9:   84 24 60 02                       cmp     rax, 0x00
     1fd:   00 00 01 00 00 00                 jnz     0x0000020f
     203:   48 8b 43 20 48 83 f8 00 0f 85     mov     qword [rsp+0x00000260], 0x00
     20f:   0c 00 00 00 48 c7 84 24 60        cmp     qword [rsp+0x00000260], 0x00
     218:   02 00 00 00 00 00                 jz      0x0000024d
     21e:   00 48 83 bc 24 60 02 00           mov     qword [rbx+0x00000020], 0x00
     226:   00 00 0f 84 2f 00 00 00           mov     qword [rbx+0x00000020], 0x01
     22e:   48 c7 43 20 00 00 00 00           mov     rax, qword [rsp+0x00000210]
     236:   48 c7 43 20 01 00 00              add     rsp, 0x268
     23d:   00 48                             pop     r12
     23f:   8b                                pop     rbp
     240:   84 24                             pop     r13
     242:   10 02                             pop     r14
     244:   00 00                             pop     r15
     246:   48                                pop     rbx
     247:   81                                ret
     248:   c4 68 02 00 00                    jmp     0x0000006d
     24d:   41 5c 5d 41 5d 41 5e 41           mov     rdx, qword [rsp+0x00000240]
     255:   5f 5b c3 e9 20 fe ff ff           add     rdx, qword [rsp+0x00000258]
     25d:   48 8b 94 24 40 02 00 00           mov     qword [rsp+0x00000260], rdx
     265:   48 03 94 24 58 02 00 00           mov     rax, qword [rsp+0x00000260]
     26d:   48 89 94 24 60 02 00              add     rsp, 0x268
     274:   00 48                             pop     r12
     276:   8b                                pop     rbp
     277:   84 24                             pop     r13
     279:   60 02                             pop     r14
     27b:   00 00                             pop     r15
     27d:   48                                pop     rbx
     27e:   81                                ret
     27f:   c4 68 02 00 00                    jmp     0x0000006d
     284:   41 5c 5d 41 5d 41 5e 41           mov     rax, qword [rsp+0x00000210]
     28c:   5f 5b c3 e9 e9 fd ff              add     rsp, 0x268
     293:   ff 48                             pop     r12
     295:   8b                                pop     rbp
     296:   84 24                             pop     r13
     298:   10 02                             pop     r14
     29a:   00 00                             pop     r15
     29c:   48                                pop     rbx
     29d:   81                                ret
     29e:   c4 68 02 00 00 41 5c              mov     rax, 0x00
     2a5:   5d 41 5d 41 5e 41 5f              add     rsp, 0x268
     2ac:   5b c3                             pop     r12
     2ae:   48                                pop     rbp
     2af:   c7 c0                             pop     r13
     2b1:   00 00                             pop     r14
     2b3:   00 00                             pop     r15
     2b5:   48                                pop     rbx
     2b6:   81                                ret
     2b7:   c4 68 02 00 00 41 5c 5d           mov     qword [rbx+0x00000020], 0x01
     2bf:   41 5d 41 5e 41 5f 5b c3           mov     qword [rbx+0x00000028], 0x07
     2c7:   48 c7 43 20 01 00 00              mov     rax, 0x00
     2ce:   00 48 c7 43 28 07 00              add     rsp, 0x268
     2d5:   00 00                             pop     r12
     2d7:   48                                pop     rbp
     2d8:   c7 c0                             pop     r13
     2da:   00 00                             pop     r14
     2dc:   00 00                             pop     r15
     2de:   48                                pop     rbx
     2df:   81                                ret

最適化

最適化は 全く やってない。JITに関してはそんなに頑張らなくてもいいかなーと。

というか、実は VM コードに対しても現時点では全くやってない。定数の畳み込みすらやってないので、まずはそっちから。

今後

優先順位によってすぐにはできないかもしれないが、上記問題の解決はどこかでやるつもり。

それ以外、せっかく型指定できるようにしているので、native 以外での部分 JIT はできるようにした方がいいよね。逆にそれができれば native いらない。というか、単なる int 省略記法になる。JIT とは言っても、一般的なトレーシングとかではなく、コンパイル時にコード出力してしまうのだけどね。この辺の言葉の使い方も難しい。Just In Time の範囲がどこまでかによって変わるが、一般的に 実行時に ということのようなので、これも JIT のうちでしょう。

おわりに

こうして記事にしてみると、何気に具体的な課題が見つかっていい感じですね。予定立てます。

ではでは、今回もここまで読んでいただいてありがとうございます。最後はいつもの以下の定型フォーマットです。興味がありましたら とか いいね LGTM」 ボタンとか押してもらえるとモチベーションにつながります。どうぞよろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

商品出品画面を作る

メルカリの商品出品画面を参考にコピーを作る

参考画面メルカリ

使用する機能

  • ActiveStorage(画像投稿)リンク
  • ancestry(多階層カテゴリー)
  • active_hash(静的データ作成)

とりあえず出来たコード

new.html.haml
.sell
  %header.sell-header
    = link_to root_path do
      = image_tag 'mercari_top_logo.svg', alt: 'mercari', height: '49', width: '185'
  -#メイン部分
  %main
    %section.sell-container
      = form_with model: @item do |f|

        -# 画像部分
        .sell-container__content
          .sell-title
            %h3.sell-title__text
              出品画像
              %span.sell-title__require
                必須
          .sell-container__content__max-sheet 最大10枚までアップロードできます
          .sell-container__content__upload
            .sell-container__content__upload__items
              .sell-container__content__upload__items__box
                %ul#output-box
                  %div#image-input{tabindex:"0"}
                    = f.label :images, for: "item_images0", class: 'sell-container__content__upload__items__box__label', data: {label_id: 0 } do 
                      = f.file_field :images, multiple: true, class: "sell-container__content__upload__items__box__input", id: "item_images0", style: 'display: none;'
                      %pre
                        %i.fas.fa-camera.fa-lg
                        ドラッグアンドドロップ
                        またはクリックしてファイルをアップロード
          .error-messages#error-image

        -#商品名部分
        .sell-container__content
          .sell-title
            %h3.sell-title__text
              商品名
              %span.sell-title__require
                必須
          = f.text_field :name, {class:'sell-container__content__name', required: "required", placeholder: '商品名(必須 40文字まで)'}
          .error-messages#error-name

          .sell-title
            %h3.sell-title__text
              商品の説明
              %span.sell-title__require
                必須
          = f.text_area :text,{class: 'sell-container__content__description', required: "required", rows: '7', maxlength: '1000', placeholder: text_placeholder}
          -# placeholderでtems_helperを呼び出す
          .sell-container__content__word-count
            %span#word-count
              0
            &#47;1000
          .error-messages#error-text

        -# 詳細部分
        .sell-container__content
          %h3.sell-sub-head 商品の詳細
          .sell-container__content__details
            .sell-title
              %h3.sell-title__text
                カテゴリー
                %span.sell-title__require
                  必須
            .sell-collection_select
              = f.label :category_id, {class: 'sell-collection_select__label'} do
                = f.collection_select :category_id, @category_parent, :id, :name, {prompt: "選択して下さい"},{ class: 'sell-collection_select__input', id: 'category-select', required: "required"}
                %i.fas.fa-chevron-down
            .error-messages#error-category

            .sell-title
              %h3.sell-title__text
                商品の状態
                %span.sell-title__require
                  必須
            .sell-collection_select
              = f.label :condition_id, {class: 'sell-collection_select__label'} do
                = f.collection_select :condition_id, Condition.all, :id, :condition, {prompt: '選択して下さい'},{ class: 'sell-collection_select__input', id: 'condition-select', required: "required"}
                %i.fas.fa-chevron-down
            .error-messages#error-condition

        -# 配送部分
        .sell-container__content
          %h3.sell-sub-head
            %p 配送について
            = link_to '/delivery',target: '_blank',class: 'sell-sub-head__guides-link' do
              %i.far.fa-question-circle
          .sell-container__content__delivery
            .sell-title
              %h3.sell-title__text
                配送料の負担
                %span.sell-title__require
                  必須
            .sell-collection_select
              = f.label :deliverycost_id, {class: 'sell-collection_select__label'} do
                = f.collection_select :deliverycost_id, Deliverycost.all, :id, :payer, {prompt: '選択して下さい'},{ class: 'sell-collection_select__input', id: 'deliverycost-select', required: "required"}
                %i.fas.fa-chevron-down
            .error-messages#error-deliverycost

            .sell-title
              %h3.sell-title__text
                発送元の地域
                %span.sell-title__require
                  必須
            .sell-collection_select
              = f.label :pref_id, class: 'sell-collection_select__label' do
                = f.collection_select :pref_id, Pref.all, :id, :name, {prompt: '選択して下さい'},{ class: 'sell-collection_select__input', id: 'pref-select', required: "required"}
                %i.fas.fa-chevron-down
            .error-messages#error-pref

            .sell-title
              %h3.sell-title__text
                発送までの日数
                %span.sell-title__require
                  必須
            .sell-collection_select
              = f.label :delivery_days_id, class: 'sell-collection_select__label' do
                = f.collection_select :delivery_days_id, DeliveryDays.all, :id, :days, {prompt: '選択して下さい'},{ class: 'sell-collection_select__input', id: 'delivery_days-select', required: "required"}
                %i.fas.fa-chevron-down
            .error-messages#error-delivery_days

        -# 価格部分
        .sell-container__content
          %h3.sell-sub-head
            %p 販売価格(300〜9,999,999)
            = link_to '/price',target: '_blank', class: 'sell-sub-head__guides-link' do
              %i.far.fa-question-circle
          .sell-container__content__price
            .sell-title
              %h3.sell-title__text
                販売価格
                %span.sell-title__require
                  必須
            .sell-container__content__price__form
              = f.label :price, class: 'sell-container__content__price__form__label' do
                ¥
                = f.number_field :price, {placeholder: '0', value: '', autocomplete:"off", class: 'sell-container__content__price__form__box', required: "required"}
          .error-messages#error-price

          .sell-container__content__commission
            .sell-container__content__commission__left
              販売手数料 (10%)
            .sell-container__content__commission__right.sell-container__content__profit
            .sell-container__content__profit__left
              販売利益
            .sell-container__content__profit__right.submit-btn
            = f.submit '出品する', class: 'submit-btn__sell-btn'
            = link_to 'もどる', root_path, class: 'submit-btn__return-btn'
          .attention-box
            %p
              禁止されている
              = link_to '行為', '/prohibited_conduct', target: '_blank'
              および
              = link_to '出品物', '/prohibited_item', target: '_blank'
              を必ずご確認ください。
              = link_to '偽ブランド品', '/counterfeit_goods', target: '_blank'= link_to '盗品物', '/stolen_goods', target: '_blank'
              などの販売は犯罪であり、法律により処罰される可能性があります。また、出品をもちまして
              = link_to '加盟店規約', '/seller_terms', target: '_blank'
              に同意したことになります。

  %footer.sell-footer
    %nav
      %ul.clearfix
        %li
          = link_to '#' do
            プライバシーポリシー
        %li
          = link_to '#' do
            メルカリ利用規約
        %li
          = link_to '#' do
            特定商取引に関する表記
    = link_to root_path, class: 'footer__logo' do
      = image_tag 'logo-gray.svg', alt: 'mercari', height: '65', width: '80'
    %p
      %small
        &copy; Mercari, Inc.
items_new.scss
a {
  color: inherit;
  text-decoration: none;
  box-sizing: border-box;
}
img {
  vertical-align: middle;
  box-sizing: border-box;
}
.error-messages{
  color: #ff0211;
  font-size: 14px;
  line-height: 1.4em;
  margin: 16px 0;
  box-sizing: border-box;
}

.sell-title{
  align-items: center;
  margin: 0!important;
  box-sizing: border-box;
  &__text{
    font-size: 14px;
    font-weight: 600;
    line-height: 1.4em;
  }
  &__require{
    margin-left: 8px;
    font-size: 12px;
    padding: 0 4px;
    background-color: #ff0211;
    color: #fff;
    border-radius: 2px;
    display: inline-block;
    font-style: normal;
    font-weight: 600;
    line-height: 1.4em;
    margin: 0;
  }
}

.sell-sub-head{
  box-sizing: border-box;
  color: rgb(136, 136, 136);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.4em;
  margin-bottom: 24px;
  display: flex;
  &__guides-link{
    color: rgb(0, 149, 238);
    margin-left: 4px;
  }
}

.sell-collection_select{
  box-sizing: border-box;
  margin-top: 16px;
  &__label{
    display: inline-block;
    position: relative;
    width: 100%;
    .fas.fa-chevron-down{
      box-sizing: border-box;
      pointer-events: none;
      position: absolute;
      right: 16px;
      top: 40%;
      color: rgb(136, 136, 136);
      height: 48px;
    }
  }
  &__input{
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-color: #fff;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
  color: #222;
  font-size: 16px;
  height: 48px;
  line-height: 1;
  margin: 0;
  outline: none;
  padding: 0 56px 0 16px;
  width: 100%;
  }
}
// ここより上は繰り返し使用するパーツ

.sell {
  box-sizing: border-box;
  position: relative;
  color: rgb(51, 51, 51);
  background-color: rgb(245, 245, 245);
  font-family: Arial, 游ゴシック体, YuGothic, メイリオ, Meiryo, sans-serif;
  font-size: 14px;
  line-height: 1;
  box-sizing: border-box;
  .sell-header{
    box-sizing: border-box;
    height: 128px;
    align-items: center;
    display: flex;
    justify-content: center;
  }
  .sell-container{
    box-sizing: border-box;
    max-width: 700px;
    width: 100%;
    margin: 0px auto;
    background-color: rgb(255, 255, 255);

    // 画像部分
    &__content{
      height: auto;
      padding: 40px;
      border-bottom: 1px;
      border-bottom-color: #efefef;
      border-bottom-style: solid;
      &__max-sheet{
        margin-top: 16px;
      }
      &__upload{
        margin-top: 16px;
        display: flex;
        flex-wrap: wrap;
        &__items{
          height: auto;
          width: 100%;

          &__box{
            height: auto;
            align-content: center;
            align-items: center;
            cursor: pointer;
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            position: relative;
            border-width: 1px;

            #output-box{
              box-sizing: border-box;
              display: flex;
              flex-wrap: wrap;
              width: 100%;
              height: auto;

              .preview-image{
                box-sizing: border-box;
                height: 150px;
                width: 20%;
                padding: 0px 4px;
                margin-top: 8px;

                &__figure{
                  margin:0 auto;
                  height: 118px;
                  background-color: rgb(245, 245, 245);
                  img{
                    box-sizing: border-box;
                    width: 100%;
                    height: 100%;
                    object-fit: contain;
                  }
                }
                &__button{
                  border-top-width: 1px;
                  border-top-color: rgb(204, 204, 204);
                  border-top-style: solid;
                  background-color: rgb(245, 245, 245);
                  justify-content: space-around;
                  display: flex;
                  align-items: center;
                  height: 32px;
                  color: rgb(0, 149, 238);
                }
              }

              #image-input{
                box-sizing: border-box;
                height: 100%;
                -webkit-flex: 1;
                flex: 1;
                margin-top: 8px;

                .sell-container__content__upload__items__box__label{
                  box-sizing: border-box;
                  background-color: rgb(245, 245, 245);
                  height: 150px;
                  border-width: 1px;
                  border-style: dashed;
                  border-color: rgb(204, 204, 204);
                  text-align: center;
                  display: flex;
                  align-items: center;
                  justify-content: center;
                  i{
                    box-sizing: border-box;
                    margin-bottom: 8px;
                  }
                }
              }
            }
          }
        }
      }
    }

    // 商品名部分
    &__content{
      &__name{
        margin-top: 16px;
        border: 1px solid #ccc;
        border-radius: 4px;
        box-sizing: border-box;
        height: 48px;
        padding: 0 16px;
        width: 100%;
      }
      &__description{
        margin-top: 16px;
        border: 1px solid #ccc;
        border-radius: 4px;
        box-sizing: border-box;
        padding: 16px;
        width: 100%;
        font-size: 16px;
        display: block;
      }
      &__word-count{
        text-align: right;
        color: #888;
        font-size: 12px;
        line-height: 1.4em;
      }
    }
    // 詳細は共通パーツのみ
    // 配送は共通パーツのみ
    // 価格部分
    &__content{
      &__price{
        -webkit-box-align: center;
        align-items: center;
        box-sizing: content-box;
        display: flex;
        height: 46px;
        justify-content:space-between;
        &form__label{
          font-size: 14px;
        }
        &__form__box{
          border: 1px solid #ccc;
          border-radius: 4px;
          height: 48px;
          margin-left: 8px;
          padding: 0 16px;
          width: 300px;
          align-items: center;
          display: inline-flex;
          text-align: right;
        }
      }
      #error-price{
        box-sizing: border-box;
        text-align: right;
      }
      &__commission{
        display: flex;
        justify-content:space-between;
        height: 70px;
        padding: 12px 0px;
        align-items: center;
        border-bottom: 1px;
        border-bottom-color: #efefef;
        border-bottom-style: solid;
      }
      &__profit{
        display: flex;
        justify-content:space-between;
        height: 70px;
        padding: 12px 0px;
        align-items: center;
      }
    }
    .submit-btn{
      box-sizing: border-box;
      margin: 0 auto;
      width: 360px;
      margin-bottom: 32px;
      &__sell-btn{
        background-color: #ea352d;
        color: #fff;
        margin-bottom: 24px;
        width: 100%;
        font-size: 17px;
        height: 48px;
        font-weight: 600;
        border-radius: 4px;
      }
      &__return-btn{
        background-color: #ccc;
        color: #222;
        width: 100%;
        font-size: 17px;
        height: 48px;
        font-weight: 600;
        padding: 14px 0;
        border-radius: 4px;
        display: inline-block;
        text-align: center;
      }
    }
    .attention-box{
      box-sizing: border-box;
      font-size: 12px;
      line-height: 1.4em;
      word-break: keep-all;
      a{
        box-sizing: border-box;
        color: #0095ee;
      }
    }
  }
  .sell-footer{
    box-sizing: border-box;
    padding: 40px 0px;
    text-align: center;
    nav{
      box-sizing: border-box;
      .clearfix{
        box-sizing: border-box;
        display: inline-block;
        font-size: 12px;
        display: flex;
        justify-content: center;
        margin-bottom: 40px;
        li{
          box-sizing: border-box;
          margin: 0px 8px;
        }
      }
    }
  }
}
items_new.js
$(document).on('turbolinks:load', function(){
  // 画像が選択された時プレビュー表示、inputの親要素のulをイベント元に指定
  $('#image-input').on('change', function(e){

    //ファイルオブジェクトを取得する
    let files = e.target.files;
    $.each(files, function(index, file) {
      let reader = new FileReader();

      //画像でない場合は処理終了
      if(file.type.indexOf("image") < 0){
        alert("画像ファイルを指定してください。");
        return false;
      }
      //アップロードした画像を設定する
      reader.onload = (function(file){
        return function(e){
          let imageLength = $('#output-box').children('li').length;
          // 表示されているプレビューの数を数える

          let labelLength = $("#image-input>label").eq(-1).data('label-id');
          // #image-inputの子要素labelの中から最後の要素のカスタムデータidを取得

          // プレビュー表示
          $('#image-input').before(`<li class="preview-image" id="upload-image${labelLength}" data-image-id="${labelLength}">
                                      <figure class="preview-image__figure">
                                        <img src='${e.target.result}' title='${file.name}' >
                                      </figure>
                                      <div class="preview-image__button">
                                        <a class="preview-image__button__edit" href="">編集</a>
                                        <a class="preview-image__button__delete" data-image-id="${labelLength}">削除</a>
                                      </div>
                                    </li>`);
          $("#image-input>label").eq(-1).css('display','none');
          // 入力されたlabelを見えなくする

          if (imageLength < 9) {
            // 表示されているプレビューが9以下なら、新たにinputを生成する
            $("#image-input").append(`<label for="item_images${labelLength+1}" class="sell-container__content__upload__items__box__label" data-label-id="${labelLength+1}">
                                        <input multiple="multiple" class="sell-container__content__upload__items__box__input" id="item_images${labelLength+1}" style="display: none;" type="file" name="item[images][]">
                                        <i class="fas fa-camera fa-lg"></i>
                                      </label>`);
          };
        };
      })(file);
      reader.readAsDataURL(file);
    });
  });

  //削除ボタンが押された時
  $(document).on('click', '.preview-image__button__delete', function(){
    let targetImageId = $(this).data('image-id');
    // イベント元のカスタムデータ属性の値を取得
    $(`#upload-image${targetImageId}`).remove();
    //プレビューを削除
    $(`[for=item_images${targetImageId}]`).remove();
    //削除したプレビューに関連したinputを削除

    let imageLength = $('#output-box').children('li').length;
    // 表示されているプレビューの数を数える
    if (imageLength ==9) {
      let labelLength = $("#image-input>label").eq(-1).data('label-id');
      // 表示されているプレビューが9なら,#image-inputの子要素labelの中から最後の要素のカスタムデータidを取得
      $("#image-input").append(`<label for="item_images${labelLength+1}" class="sell-container__content__upload__items__box__label" data-label-id="${labelLength+1}">
                                  <input multiple="multiple" class="sell-container__content__upload__items__box__input" id="item_images${labelLength+1}" style="display: none;" type="file" name="item[images][]">
                                  <i class="fas fa-camera fa-lg"></i>
                                </label>`);
    };
  });

  // f.text_areaの文字数カウント
  $("textarea").keyup(function(){
    let txtcount = $(this).val().length;
    $("#word-count").text(txtcount);
  });

  //販売価格入力時の手数料計算
  $('#item_price').keyup(function(){
    let price= $(this).val();
    if (price >= 300 && price <= 9999999){
      let fee = Math.floor(price * 0.1);
      // 小数点以下切り捨て
      let profit = (price - fee);
      $('.sell-container__content__commission__right').text('¥'+fee.toLocaleString());
      // 対象要素の文字列書き換える
      $('.sell-container__content__profit__right').text('¥'+profit.toLocaleString());
    } else{
      $('.sell-container__content__commission__right').html('');
      $('.sell-container__content__profit__right').html('');
    }
  });

  // 各フォームの入力チェック
  $(function(){
    //画像
    $('#image-input').on('focus',function(){
      $('#error-image').text('');
      $('#image-input').on('blur',function(){
        $('#error-image').text('');
        let imageLength = $('#output-box').children('li').length;
        if(imageLength ==''){
          $('#error-image').text('画像がありません');
        }else if(imageLength >10){
          $('#error-image').text('画像を10枚以下にして下さい');
        }else{
          $('#error-image').text('');
        }
      });
    });

    //送信しようとした時
    $('form').on('submit',function(){
      let imageLength = $('#output-box').children('li').length;
      if(imageLength ==''){
        $('body, html').animate({ scrollTop: 0 }, 500);
        $('#error-image').text('画像がありません');
      }else if(imageLength >10){
        $('body, html').animate({ scrollTop: 0 }, 500);
        $('#error-image').text('画像を10枚以下にして下さい');
      }else{
        return false;
      }
    });

     //画像を削除した時
    $(document).on('click','.preview-image__button__delete',function(){
      let imageLength = $('#output-box').children('li').length;
      if(imageLength ==''){
        $('#error-image').text('画像がありません');
      }else if(imageLength >10){
        $('#error-image').text('画像を10枚以下にして下さい');
      }else{
        $('#error-image').text('');
      }
    });

    //商品名
    $('.sell-container__content__name').on('blur',function(){
      let value = $(this).val();
      if(value == ""){
        $('#error-name').text('入力してください');
        $(this).css('border-color','red');
      }else{
        $('#error-name').text('');
        $(this).css('border-color','rgb(204, 204, 204)');
      }
    });

    //商品説明
    $('.sell-container__content__description').on('blur',function(){
      let value = $(this).val();
      if(value == ""){
        $('#error-text').text('入力してください');
        $(this).css('border-color','red');
      }else{
        $('#error-text').text('');
        $(this).css('border-color','rgb(204, 204, 204)');
      }
    });

    //カテゴリー
    $('#category-select').on('blur',function(){
      let value = $(this).val();
      if(value == ""){
        $('#error-category').text('選択して下さい');
        $(this).css('border-color','red');
      }else{
        $('#error-category').text('');
        $(this).css('border-color','rgb(204, 204, 204)');
      }
    });

    //状態
    $('#condition-select').on('blur',function(){
      let value = $(this).val();
      if(value == ""){
        $('#error-condition').text('選択して下さい');
        $(this).css('border-color','red');
      }else{
        $('#error-condition').text('');
        $(this).css('border-color','rgb(204, 204, 204)');
      }
    });

    //送料負担
    $('#deliverycost-select').on('blur',function(){
      let value = $(this).val();
      if(value == ""){
        $('#error-deliverycost').text('選択して下さい');
        $(this).css('border-color','red');
      }else{
        $('#error-deliverycost').text('');
        $(this).css('border-color','rgb(204, 204, 204)');
      }
    });

    //発送元
    $('#pref-select').on('blur',function(){
      let value = $(this).val();
      if(value == ""){
        $('#error-pref').text('選択して下さい');
        $(this).css('border-color','red');
      }else{
        $('#error-pref').text('');
        $(this).css('border-color','rgb(204, 204, 204)');
      }
    });

    //発送までの日数
    $('#delivery_days-select').on('blur',function(){
      let value = $(this).val();
      if(value == ""){
        $('#error-delivery_days').text('選択して下さい');
        $(this).css('border-color','red');
      }else{
        $('#error-delivery_days').text('');
        $(this).css('border-color','rgb(204, 204, 204)');
      }
    });

    //価格
    $('.sell-container__content__price__form__box').on('blur',function(){
      let value = $(this).val();
      if(value < 300 || value > 9999999){
        $('#error-price').text('300以上9999999以下で入力してください');
        $(this).css('border-color','red');
      }else{
        $('#error-price').text('');
        $(this).css('border-color','rgb(204, 204, 204)');
      }
    });

  });
});
items.controller.rb
def new
    @item = Item.new
    @category_parent =  Category.where("ancestry is null")
  end

  def create
    @item = Item.new(item_params)
    if @item.save
      redirect_to root_path
    else
      render :new
    end
  end



  private

  def item_params
    params.require(:item).permit(:name, :text, :category_id, :condition_id, :deliverycost_id, :pref_id, :delivery_days_id, :price, images: []).merge(user_id: current_user.id, boughtflg_id:"1")
  end
end
item.rb(モデル)
class Item < ApplicationRecord

  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :condition
  belongs_to_active_hash :pref
  belongs_to_active_hash :deliverycost
  belongs_to_active_hash :delivery_days
  belongs_to_active_hash :boughtflg
  # 上記active_hashのアソシエーション
  validate :images_presence
  validates :name, :text, :category_id, :condition_id, :deliverycost_id, :pref_id, :delivery_days_id, :boughtflg_id, presence: true
  validates :price, presence: true, inclusion: 300..9999999

  has_many_attached :images
  belongs_to :user, foreign_key: 'user_id'
  # optional: true後で消す belongs_toのnotnull制約解放のため使用している
  belongs_to :category

  #imageのバリデーション
  def images_presence
    if images.attached?
      # inputに保持されているimagesがあるかを確認
      if images.length > 10
        errors.add(:image, '10枚まで投稿できます')
      end
    else
      errors.add(:image, '画像がありません')
    end
  end
end

カテゴリーの2階層以降がまだ未実装でした・・・

とりあえず、後から実装するとして、先ずは画像投稿から

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

7つのアクション

今回は、コントローラで使用する7つのアクションについて紹介します!
アクションとは、ルーティングがリクエストを受け取った時に動くものです

アクション毎に行われる処理を分けて記述することによって、役割をわかりやすく分類化できます!

アクション名 どんなリクエストに対応して動く?
index 一覧ページを表示する
new 新規投稿ページを表示する
create データの投稿を行う
show 個別の詳細ページを表示する
edit 投稿編集ページを表示する
update データの編集を行う
destroy データの削除を行う

この7つのアクションは基本になる所なので、ぜひ覚えて活用できるようにしてみましょう^^!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】アクセス制限を記述する手順と、その思考手順をまとめてみました

はじめに

Railsを学習した直後のアウトプット時、いざアクセス制限機能を実装しようと試みた際に、何をどうしたらいいのか思い浮かばなかったのが悔しかったため
次に同じ轍を踏まないよう、自分用にアクセス制限の実装順序を整理してみました。

まず、アクセス制限って「何を」実装する機能なのん?

主にWebアプリケーションにおいてログイン機能を実装した後
ユーザーがログインしている場合と、ログインしていない場合とで、使える機能を制限する機能。

それじゃあ、「どうすれば」実装できるのか?

例えば
ユーザーがログインしている場合で一つ条件式を記述し、制限用の処理を記述する。
ユーザーがログインしていない場合でもう一つ条件式を書き、制限用の処理を記述する。

 if #(ユーザーがログインしている場合の条件式)
   redirect_to("URL") #制限用の処理
 end

 

コードは「どこに」記述するん?

簡単な掲示板アプリ作成を例として、「ユーザーがログインしていない場合」のアクセス制限機能を実装したいとします。

1.Railでは、主にルーティング→アクション→ビューの順でデータのやり取りが行われるので
2.例えば、ログインしていないユーザー投稿機能のアクセス制限を実装したい場合は
3.投稿機能用のアクションにデータが渡されたタイミングで、ログインしていないユーザーを弾けば良いということになりますので
4.投稿用のアクション最上部に、「ユーザーがログインしていない場合」の条件式と処理を記述してあげます。
5.そうすれば、投稿用のアクションが実行される前に、アクセス制限の処理が実行されます。

よってコードをどこに記述すればいいのかと言うと、制限を実装したい機能のアクション内の上部、ということになります。

posts_controller.rb
 def #投稿用アクション
   if #(ユーザーがログインしていない場合の条件式)
     redirect_to("URL") #指定したURLに飛ばす
   end

   #投稿用の処理

 end

before_actionとは文字通り、actionのbeforeに実行される

ただ投稿制限の他にも、投稿を閲覧する機能や投稿を編集する機能なども制限したい場合もあるかと思います。
その際、制限したいアクション全てに対し条件式を書こうとするのは、同じ記述をすることになり好ましくない上に面倒臭いです……。

そこで、全てのアクションに対して一括で処理を行う方法が存在します。

どのように全てのアクションに対し一括で処理を行うかというと、applicationコントローラに処理を記述してあげます。
applicationコントローラとは、全てのコントローラを管理している親のような存在です。
スクリーンショット 2020-03-24 19.52.54.png
画像の一番上にあるのが、applicationコントローラで
その直下に他のコントローラが羅列され、applicationコントローラの処理を継承しています。
そのため、applicationコントローラで記述された処理は、別のコントローラで実行されることになります。

ただし注意する点があり、「before_action :メソッド名」という処理をコントローラ内の上部に記述してあげる必要があります。
言葉だけでは伝わりにくいと思いますので、コード例を記述してみます。

application_controller.rb
before_action :#メソッド名

# ログインしていない場合のアクセス制限
def #メソッド名
  if  #(アクセスしたユーザーがログインしていない場合の条件式)
    redirect_to("URL") #指定したURLに飛ばす
  end
end

このようにapplicationコントローラ内に「before_action :メソッド名」と記述してあげることで、メソッド内で定義した処理が全てのコントローラのアクションが実行される前に実行されることになります。

before_actiontに関しては文字通り、全てのアクションの前に実行される処理という風に覚えておけば、用途をしっかり理解して使えるようになるかと思います。

before_actionを特定のアクションにだけ適用させる方法があるだと!?

全てのアクションへ処理が適用されるとは言っても、特定のアクションにだけアクセス制限を適用させたいという場合もあるでしょう。
そう言った場合には、特定のアクションにだけアクセス制限の処理を適用させる方法が存在します。

applicationコントローラ内で記述した「before_action :メソッド名」を
適用させたいアクションが記述されているコントローラの上部に記述した上で
メソッド名に続けて「, {only: [:アクション名, :アクション名,...]}」として
[ ]内に、各コントローラ内のアクセス制限を適用させたいアクション名を記述してあげることで、特定のアクションにだけアクセス制限を適用することができます。

posts_controller.rb
 before_action :#メソッド名, {only: [:投稿用アクション名, 投稿表示アクション名,...]}

 def #投稿用アクション名
   #処理
 end

 def #投稿表示用アクション名
   #処理
 end

こうすることにより、各々アクションが実行される前に、before_actionの処理が呼び出され、各アクションへのアクセス制限が可能となります。

まとめ

「アウトプットでアクセス制限を実装したい!」となった場合、考える順序として

1.まずアクセス制限とは、ログイン機能実装後に「ログインしているユーザーへの機能制限」や「ログインしていないユーザーへの機能制限」を行うこと。
2.「投稿を制限したい」「投稿の閲覧を制限したい」など、「どんなアクセス制限」を実装したいか思い浮かべる。
3.もし投稿機能を制限したいなら、「投稿用のアクション」など、コードを記述すべき場所を思い浮かべる。
4.該当アクション内の上部に、「ユーザーがログインしていない場合は〜〜の処理を行う」などの「条件式」を記述する。
5.他の機能も制限したいけれど、複数個に渡って同じ記述をするのは面倒臭いな……。
6.複数アクションに制限を掛けたい場合は、applicationコントローラを使えば、一つの記述で済むことを思い出す。
7.applicationコントローラ内に、条件式とアクセス制限の処理を記述する。
8.各コントローラ内のアクションが実行される前に、アクセス制限処理を実行しなければならないことを思い出す(before_action :メソッド名)。
(9.特定のアクションにアクセス制限を掛けたい場合の処理は、「 before_action :メソッド名, {only: [:アクション名, :アクション名,...]} 」を、各コントローラ内の上部に記述する。)


最後に

周りくどい説明をしてきましたが、このあたりは基礎の基礎ですし
始めはRailsそのもののデータやり取りの仕組みを理解することが重要だと思ったので、今回のように遠回りするような解説をさせて頂きました。
僕も慣れるまでは、この思考手順で学習を進めるつもりです。

参考

Progate

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】アクセス制限を記述する手順をまとめてみました!

はじめに

Railsを学習した直後のアウトプット時、いざアクセス制限機能を実装しようと試みた際に、何をどうしたらいいのか思い浮かばなかったのが悔しかったため
次に同じ轍を踏まないよう、自分用にアクセス制限の実装順序を整理してみました。

まず、アクセス制限って「何を」実装する機能なのん?

主にWebアプリケーションにおいてログイン機能を実装した後
ユーザーがログインしている場合と、ログインしていない場合とで、使える機能を制限する機能。

それじゃあ、「どうすれば」実装できるのか?

例えば
ユーザーがログインしている場合で一つ条件式を記述し、制限用の処理を記述する。
ユーザーがログインしていない場合でもう一つ条件式を書き、制限用の処理を記述する。

 if #(ユーザーがログインしている場合の条件式)
   redirect_to("URL") #制限用の処理
 end

 

コードは「どこに」記述するん?

簡単な掲示板アプリ作成を例として、「ユーザーがログインしていない場合」のアクセス制限機能を実装したいとします。

1.Railsのデータのやり取りは、基本的にルーティング→アクション→ビューの順で行われ
2.例えば、ログインしていないユーザー投稿機能のアクセス制限を掛けたい場合は
3.投稿用のアクション最上部に、「ユーザーがログインしていない場合」の条件式と処理を記述してあげます。
4.そうすれば、投稿用のアクションが実行される前に、アクセス制限の処理が実行されます。

なので、位置的に言えばアクション内の上部に書けばいいということなのですね。

posts_controller.rb
 def #投稿用アクション
   if #(ユーザーがログインしていない場合の条件式)
     redirect_to("URL") #指定したURLに飛ばす
   end

   #投稿用の処理

 end

before_actionとは文字通り、actionのbeforeに実行される

ただ投稿制限の他にも、投稿を閲覧する機能や投稿を編集する機能なども制限したい場合もあるかと思います。
その際、制限したいアクション全てに対し条件式を書こうとするのは、同じ記述をすることになり好ましくない上に面倒臭いです……。

そこで、全てのアクションに対して一括で処理を行う方法が存在します。

どのように全てのアクションに対し一括で処理を行うかというと、applicationコントローラに処理を記述してあげます。
applicationコントローラとは、全てのコントローラを管理している親のような存在です。
スクリーンショット 2020-03-24 19.52.54.png
画像の一番上にあるのが、applicationコントローラで
その直下に他のコントローラが羅列され、applicationコントローラの処理を継承しています。
そのため、applicationコントローラで記述された処理は、別のコントローラで実行されることになります。

ただし注意する点があり、「before_action :メソッド名」という処理をコントローラ内の上部に記述してあげる必要があります。
言葉だけでは伝わりにくいと思いますので、コード例を記述してみます。

application_controller.rb
before_action :#メソッド名

# ログインしていない場合のアクセス制限
def #メソッド名
  if  #(アクセスしたユーザーがログインしていない場合の条件式)
    redirect_to("URL") #指定したURLに飛ばす
  end
end

このようにapplicationコントローラ内に「before_action :メソッド名」と記述してあげることで、メソッド内で定義した処理が全てのコントローラのアクションが実行される前に実行されることになります。

before_actiontに関しては文字通り、全てのアクションの前に実行される処理という風に覚えておけば、用途をしっかり理解して使えるようになるかと思います。

before_actionを特定のアクションにだけ適用させる方法があるだと!?

全てのアクションへ処理が適用されるとは言っても、特定のアクションにだけアクセス制限を適用させたいという場合もあるでしょう。
そう言った場合には、特定のアクションにだけアクセス制限の処理を適用させる方法が存在します。

applicationコントローラ内で記述した「before_action :メソッド名」を
適用させたいアクションが記述されているコントローラの上部に記述した上で
メソッド名に続けて「, {only: [:アクション名, :アクション名,...]}」として
[ ]内に、各コントローラ内のアクセス制限を適用させたいアクション名を記述してあげることで、特定のアクションにだけアクセス制限を適用することができます。

posts_controller.rb
 before_action :#メソッド名, {only: [:投稿用アクション名, 投稿表示アクション名,...]}

 def #投稿用アクション名
   #処理
 end

 def #投稿表示用アクション名
   #処理
 end

こうすることにより、各々アクションが実行される前に、before_actionの処理が呼び出され、各アクションへのアクセス制限が可能となります。

まとめ

「アウトプットでアクセス制限を実装したい!」となった場合、考える順序として

1.まずアクセス制限とは、ログイン機能実装後に「ログインしているユーザーへの機能制限」や「ログインしていないユーザーへの機能制限」を行うこと。
2.「投稿を制限したい」「投稿の閲覧を制限したい」など、「どんなアクセス制限」を実装したいか思い浮かべる。
3.例えば、投稿を制限したいなら「投稿用のアクション」など、コードを記述すべき場所を押さえる。
4.該当アクション内に、「ユーザーがログインしていない場合は〜〜の処理を行う」などの「条件式」を記述する。
5.ただし複数個に渡って同じ記述をするのは面倒臭い……。
6.複数アクションに制限を掛けたい場合は、applicationコントローラを使えば、一つの記述で済むことを思い出す。
7.applicationコントローラ内に、条件式とアクセス制限の処理を記述する。
8.各コントローラ内のアクションが実行される前に、アクセス制限処理を実行しなければならないことを思い出す(before_action :メソッド名)。
(9.特定のアクションにアクセス制限を掛けたい場合の処理は、「 before_action :メソッド名, {only: [:アクション名, :アクション名,...]} 」を、各コントローラ内の上部に記述する。)


参考

Progate

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

exifrを使用したフォルダ内画像のexifデータ一括抽出

プログラミング初心者です。
任意のフォルダ内に格納されている画像のexifを抽出するのに手間取ったため、備忘録として残します。

require 'exifr/jpeg'
require 'csv'

puts Dir.pwd

dir_name = "/Users/任意のフォルダパス"

Dir.chdir(dir_name)
dir = Dir.open(dir_name)
puts Dir.pwd

CSV.open('exif.csv','w') do |csv|
 csv << ['model','lens','f','exposure','iso','date']
  name = p Dir.glob('*.JPG')
  name.each do |syashin|
   exif = EXIFR::JPEG.new(syashin)
    a = exif.model
    b = exif.lens_model
    c = exif.f_number.to_f
    d = exif.exposure_time
    e = exif.iso_speed_ratings
    f = exif.date_time
    csv << [a,b,c,d,e,f]
   end
end

画像と同じフォルダにcsvファイルが作成されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[WIP]RuboCop規約一覧表

一覧

区分 規約 Cop数
Bundler Bundlerファイル 4
Gemspec gemspecファイル 4
Layout インデント、行端揃え、スペースの一貫性 88
Lint 曖昧性と潜在エラー 82
Metrics プロパティ 9
Migration rubocop.yml内のCop名 1
Naming 命名規則 16
Security メソッド呼び出し、構造に潜むセキュリティホール 5
Style Ruby Style Guideに準拠したスタイルの一貫性 172

Bundler

Cop名 解析対象 デフォルト
Bundler/DuplicatedGem Gemfile内のライブラリが重複していないか 有効
Bundler/GemComment Gemfile内のライブラリ用途を説明するコメントがあるか 無効
Bundler/InsecureProtocolSource ・シンボル引数の:gemcutter:rubygems:rubyforge が使われていないか
・ソースURLに https://rubygems.org が指定されているか
有効
Bundler/OrderedGems Gemfile内のライブラリがアルファベット昇順でソートされているか 有効

Gemspec

Cop名 解析対象 デフォルト
Gemspec/DuplicatedAssignment gemspec内の属性定義メソッド呼出しが重複していないか 有効
Gemspec/OrderedDependencies gemspec内の依存関係がアルファベット昇順でソートされているか 有効
Gemspec/RequiredRubyVersion 以下ファイル内の指定Rubyバージョンが互いに一致しているか
・gemspec: required_ruby_version
・.rubocop.yml: TargetRubyVersion
有効
Gemspec/RubyVersionGlobalsUsage gemspec内で定数 RUBY_VERSION が使われていないか 有効

Layout

Cop名 解析対象 デフォルト
Layout/AccessModifierIndentation 単体のアクセス修飾子(e.g. private)のインデントの深さが defclassmodule と同等か 有効
Layout/ArgumentAlignment 複数行メソッド呼出しにおける引数が統一されたインデントがされているか 有効
Layout/ArrayAlignment 複数行配列の要素が統一されたインデントがされているか 有効
Layout/AssignmentIndentation 代入式における複数行右辺の一行目が統一されたインデントがされているか 有効
Layout/BlockAlignment 複数行ブロックの do ~ end が統一されたインデントがされているか 有効
Layout/BlockEndNewline 複数行ブロックの do ~ end{ ~ } が独立した行に書かれているか 有効
Layout/CaseIndentation ・case文における case ~ when ~ end が統一されたインデントがされているか
・代入式における右辺のcase文の揃い: デフォルトは EnforcedStyle: case
有効
Layout/ClassStructure クラススコープ(class ~ end)内の要素が以下の構造に準拠しているか
・moduleのミックスイン: includeprependextend
・定数定義
・クラス関係定義: belongs_tohas_onehas_many
・クラスマクロ: attr_accessorattr_writerattr_reader
・検証等その他マクロ: validatesvalidate
・Publicクラスメソッド
・Initializer
・Publicインスタンスメソッド
・Protectedクラスマクロ: attr_accessorattr_writerattr_reader
・Protectedインスタンスメソッド
・Privateクラスマクロ: attr_accessorattr_writerattr_reader
・Privateインスタンスメソッド
無効
Layout/ClosingHeredocIndentation ヒアドキュメントの識別子の揃いと、閉じ識別子が内部要素のより内側にあるか 有効
Layout/ClosingParenthesisIndentation 複数行のメソッド定義、メソッド呼出し、グループにおける閉じ括弧 ) が統一されたインデントがされているか 有効
Layout/CommentIndentation コメント行が統一されたインデントがされているか 有効
Layout/ConditionPosition 条件文の判断条件が if,  whileuntil と同じ行に書かれているか 有効
Layout/DefEndAlignment メソッド定義スコープ(def ~ end)における end が統一されたインデントがされているか
※ デフォルトは EnforcedStyleAlignWith: start_of_line
有効
Layout/DotPosition 複数行におけるレシーバーとメソッドの区切り . が統一されたインデントがされているか
※ デフォルトは EnforcedStyle: leading
有効
Layout/ElseAlignment 条件文における else が if, whileuntil と揃っているか 有効
Layout/EmptyComment 空のコメント行が統一されたインデントがされているか
※ デフォルトは AllowBorderComment: trueAllowMarginComment: true
有効
Layout/EmptyLineAfterGuardClause ガード構文の直後に空行が存在するか 有効
Layout/EmptyLineAfterMagicComment マジックコメント最終行の直後に空行が存在するか 有効
Layout/EmptyLineBetweenDefs メソッド定義スコープ(def ~ end)間に空行が存在しないか 有効
Layout/EmptyLines 二行以上連続した空行が存在しないか 有効
Layout/EmptyLinesAroundAccessModifier アクセス修飾子(e.g. private)前後に空行が存在するか 有効
Layout/EmptyLinesAroundArguments 複数行メソッド呼出しの引数に空行が存在しないか 有効
Layout/EmptyLinesAroundBeginBody begin ~ end スコープ内に空行が存在しないか 有効
Layout/EmptyLinesAroundBlockBody ブロック内に空行が存在しないか 有効
Layout/EmptyLinesAroundClassBody クラススコープ(class ~ end)内に空行が存在しないか
※ デフォルトは EnforcedStyle: no_empty_lines
有効
Layout/EmptyLinesAroundExceptionHandlingKeywords 例外処理スコープ(begin ~ rescue ~ else ~ ensure ~ end)内に空行が存在しないか 有効
Layout/EmptyLinesAroundMethodBody メソッド定義スコープ(def ~ end)内に空行が存在しないか 有効
Layout/EmptyLinesAroundModuleBody モジュールスコープ(module ~ end)内に空行が存在しないか
※ デフォルトは EnforcedStyle: no_empty_lines
有効
Layout/EndAlignment スコープの end が統一されたインデントがされているか
※ デフォルトは EnforcedStyleAlignWith: keyword
有効
Layout/EndOfLine WindowsOSにおける改行コード
※ デフォルトは EnforcedStyle: native(WindowsOS: CR+LF / その他OS: LF)
有効
Layout/ExtraSpacing 無意味なスペースが存在しないか
※ デフォルトは AllowForAlignment: trueAllowBeforeTrailingComments: falseForceEqualSignAlignment: false
有効
Layout/FirstArgumentIndentation メソッド呼出しの第一引数にインデントがされているか
※ デフォルトは EnforcedStyle: special_for_inner_method_call_in_parentheses
有効
Layout/FirstArrayElementIndentation 複数行配列において、[ と第一要素が別々の行にある場合の第一要素にインデントがされているか
※ デフォルトは EnforcedStyle: special_inside_parentheses
有効
Layout/FirstArrayElementLineBreak 複数行配列における第一要素の前に改行がされているか 無効
Layout/FirstHashElementIndentation 複数行ハッシュにおける最初のキーにインデントがされているか
※ デフォルトは EnforcedStyle: special_inside_parentheses
有効
Layout/FirstHashElementLineBreak 複数行ハッシュにおける最初のキーの前に改行がされているか 無効
Layout/FirstMethodArgumentLineBreak メソッド呼出しの第一引数の前に改行がされているか 無効
Layout/FirstMethodParameterLineBreak メソッド定義の最初のパラメーターの前に改行がされているか 無効
Layout/FirstParameterIndentation メソッド定義の最初のパラメーターにインデントがされているか
※ デフォルトは EnforcedStyle: consistent
有効
Layout/HashAlignment 複数行ハッシュのキー、セパレーター、値にインデントがされているか
・ハッシュロケット / コンマ: デフォルトは EnforcedHashRocketStyle: keyEnforcedColonStyle: key
・メソッド呼出しの最後の引数としての扱い: デフォルトは EnforcedLastArgumentHashStyle: always_inspect
有効
Layout/HeredocArgumentClosingParenthesis メソッド呼出しの引数としてのヒアドキュメントの閉じ識別子にインデントがされているか 無効
Layout/HeredocIndentation ヒアドキュメント内部にインデントがされているか
※ デフォルトは EnforcedStyle: squiggly
有効
Layout/IndentationConsistency インデントに一貫性があるか
※ デフォルトは EnforcedStyle: normal
有効
Layout/IndentationWidth インデントにおける半角スペースの数が正しいか
※ デフォルトは半角スペース2つ
有効
Layout/InitialIndentation ファイル内の非ブロック、非コメントの最初の行にインデントがされているか 有効
Layout/LeadingCommentSpace コメントの # 識別子の直後に半角スペースが存在するか 有効
Layout/LeadingEmptyLines ファイル一行目に空行が存在しないか 有効
Layout/LineLength 一行あたりの文字数は多すぎないか 有効
Layout/MultilineArrayBraceLayout 複数行の括弧における (]} にインデントがされているか
※ デフォルトは EnforcedStyle: symmetrical
有効
Layout/MultilineArrayLineBreaks 複数行配列のそれぞれの要素が改行によって独立しているか 有効
Layout/MultilineAssignmentLayout 複数行の右辺を持つ代入式における、演算子の後に改行がされているか 無効
Layout/MultilineBlockLayout do ~ end{ ~ } ブロックのブロック引数と改行
・ブロック引数: ブロックの最初の行の do または { の後
・改行: ブロックの最初の行の do{ またはブロック引数の後
有効
Layout/MultilineHashBraceLayout 複数行ハッシュの {} にインデントがされているか
※ デフォルトは EnforcedStyle: symmetrical
有効
Layout/MultilineHashKeyLineBreaks 複数行ハッシュのキー: 値がそれぞれ改行によって独立しているか 無効
Layout/MultilineMethodArgumentLineBreaks メソッド呼出しの複数行の引数がそれぞれ改行によって独立しているか 無効
Layout/MultilineMethodCallBraceLayout 複数行メソッドの呼出しの閉じ括弧 ) にインデントがされているか
※ デフォルトは EnforcedStyle: symmetrical
有効
Layout/MultilineMethodCallIndentation .メソッド名 にインデントがされているか
※ デフォルトは EnforcedStyle: aligned
有効
Layout/MultilineMethodDefinitionBraceLayout メソッド定義の複数行のパラメーターにおける閉じ括弧 ) にインデントがされているか
※ デフォルトは EnforcedStyle: symmetrical
有効
Layout/MultilineOperationIndentation 複数行の演算式にインデントがされているか
※ デフォルトは EnforcedStyle: aligned
有効
Layout/ParameterAlignment 複数行のメソッド定義、メソッド呼出しにインデントがされているか
※ デフォルトは EnforcedStyle: with_first_parameter
有効
Layout/RescueEnsureAlignment 例外処理スコープ内の rescueensure にインデントがされているか 有効
Layout/SpaceAfterColon コロン(:)の直後に半角スペースが存在するか 有効
Layout/SpaceAfterComma コンマ(,)の直後に半角スペースが存在するか 有効
Layout/SpaceAfterMethodName メソッド名の直後に半角スペースが存在するか 有効
Layout/SpaceAfterNot 否定演算子(!)の直後に半角スペースが存在しないか 有効
Layout/SpaceAfterSemicolon セミコロン(;)の直後に半角スペースが存在するか 有効
Layout/SpaceAroundBlockParameters ブロック引数前後に半角スペースが存在するか 有効
Layout/SpaceAroundEqualsInParameterDefault メソッド定義のデフォルト引数における = 演算子の前後に半角スペースが存在するか 有効
Layout/SpaceAroundKeyword doif 等キーワード前後に半角スペースが存在するか 有効
Layout/SpaceAroundOperators 演算子前後に半角スペースが存在するか
※ デフォルトは AllowForAlignment: true
有効
Layout/SpaceBeforeBlockBraces ブロックの始まりの { 直前に半角スペースが存在するか
※ デフォルトは EnforcedStyle: space
有効
Layout/SpaceBeforeComma コンマ(,)の直前に半角スペースが存在しないか 有効
Layout/SpaceBeforeComment コメントの直線に半角スペースが存在するか 有効
Layout/SpaceBeforeFirstArg () のないメソッド呼出しで、メソッド名と引数の間に連続して半角スペースが複数存在しないか 有効
Layout/SpaceBeforeSemicolon セミコロン(;)の直前に半角スペースが存在しないか 有効
Layout/SpaceInLambdaLiteral lambdaの引数の ( の直後に半角スペースが存在しないか
※ デフォルトは EnforcedStyle: require_no_space
有効
Layout/SpaceInsideArrayLiteralBrackets 配列の [ の直後と ] の直前に半角スペースが存在しないか
※ デフォルトは EnforcedStyle: no_space
有効
Layout/SpaceInsideArrayPercentLiteral %記法の () 内に連続して半角スペースが複数存在しないか 有効
Layout/SpaceInsideBlockBraces ブロックの { の直後と } の直前に半角スペースが存在するか
※ デフォルトは EnforcedStyle: space
有効
Layout/SpaceInsideHashLiteralBraces ハッシュの { の直後と } の直前に半角スペースが存在するか
※ デフォルトは EnforcedStyle: space
有効
Layout/SpaceInsideParens ( の直後と ) の直前に半角スペースが存在しないか
※ デフォルトは EnforcedStyle: no_space
有効
Layout/SpaceInsidePercentLiteralDelimiters %記法の ( の直後と ) の直前に半角スペースが存在しないか 有効
Layout/SpaceInsideRangeLiteral Rangeの ..... の前後に半角スペースが存在しないか 有効
Layout/SpaceInsideReferenceBrackets ハッシュのキー取得における [ の直後と ] の直前に半角スペースが存在しないか
※ デフォルトは EnforcedStyle: no_space
有効
Layout/SpaceInsideStringInterpolation 式展開の { の直後と } の直前に半角スペースが存在しないか
※ デフォルトは EnforcedStyle: no_space
有効
Layout/Tab インデントにタブが使われていないか 有効
Layout/TrailingEmptyLines ファイルの最後に空行が存在するか 有効
Layout/TrailingWhitespace 無意味な半角スペースが存在しないか 有効

Lint

Cop名 解析対象 デフォルト
Lint/AmbiguousBlockAssociation メソッド呼び出しの引数に () がないことによって、ブロックの結合度が不明確になっていないか 有効
Lint/AmbiguousOperator メソッド呼び出しの第一引数に () がないことによって、演算子の意味が不明確になっていないか 有効
Lint/AmbiguousRegexpLiteral メソッド呼び出しの第一引数に () がないことによって、正規表現のエスケープシーケンスの意味が不明確になっていないか 有効
Lint/AssignmentInCondition if,  whileuntil などの条件文において、判断条件の代入式の右辺 true を代入するときに () が使われているか
※ デフォルトは AllowSafeAssignment: true
有効
Lint/BigDecimalNew 非推奨の BigDecimal.new() が使われていないか 有効
Lint/BooleanSymbol boolean型がシンボルで使われていないか(e.g. :true:false) 有効
Lint/CircularArgumentReference 調査中 有効
Lint/Debugger 開発環境以外(テスト環境、本番環境)でデバッグ用のメソッド(pry, byebug)が使われていないか 有効
Lint/DeprecatedClassMethods 非推奨のクラスメソッド(File.exists?Dir.exists?iterator?)が使われていないか 有効
Lint/DisjunctiveAssignmentInConstructor initializerで、インスタンス変数の代入に `\ =` 演算子が使われていないか
Lint/DuplicateCaseCondition case文において、when句の判断条件が重複していないか 有効
Lint/DuplicateHashKey ハッシュのキーが重複していないか 有効
Lint/DuplicateMethods メソッド定義が重複していないか 有効
Lint/EachWithObjectArgument Enumerable#each_with_object において、引数にイミュータブルな値が使われていないか
※ Enumerable#each_with_object の引数は、当該メソッドが可算集合対して行うループ処理に基づいてあるデータを作るために与えられたブロックから呼び出されるオブジェクトであるので、イミュータブルな引数は意味をなさない上、これは明らかなバグである。
有効
Lint/ElseLayout if,  whileuntil などの条件文において、else 句に条件判断が書かれていないか 有効
Lint/EmptyEnsure 例外処理において、ensure 句に処理が書かれているか 有効
Lint/EmptyExpression 変数や条件判断に空値が使われていないか 有効
Lint/EmptyInterpolation 空の式展開が使われていないか 有効
Lint/EmptyWhen case文の when句に処理が書かれているか 有効
Lint/EndInMethod 調査中 有効
Lint/EnsureReturn 例外処理において、ensure 句で return が使われていないか
※ return を明記するとコントロールフローが変わる。理由は以下の2つ。
raise された例外より優先して return が実行されるため
rescue されたが如く、キャッチした例外は通知なしに放棄される
有効
Lint/ErbNewArguments 調査中 有効
Lint/FlipFlop flip-flop演算子が含まれていないか
※ Ruby 2.6.0 ~ 非推奨
有効
Lint/FloatOutOfRange floatの数値が大きすぎないか 有効
Lint/FormatParameterMismatch Kernel#format メソッド呼出しで、渡される引数の数とデータ型が一致しているか 有効
Lint/HeredocMethodCallPosition ヒアドキュメントのメソッド呼出しにおいて、レシーバーが呼び出される順序づけが正しいか 有効
Lint/ImplicitStringConcatenation 同じ行の文字列結合が正しく行われているか 有効
Lint/IneffectiveAccessModifier アクセス修飾子(e.g. protectedprivate)が特異メソッドの定義に使われていないか
※ 特異メソッドの定義には private_class_method を使う
有効
Lint/InheritException 継承する例外のスーパークラスが正しく指定されているか
※ デフォルトは EnforcedStyle: runtime_error
有効
Lint/InterpolationCheck シングルクオーテーション内で式展開が行われていないか 有効
Lint/LiteralAsCondition if,  whileuntil などの条件文において、&&, || の中で判断条件や演算対象として使われているリテラルが正しく使われているか 有効
Lint/LiteralInInterpolation 変数やメソッド呼出し以外で不要な式展開がされていないか 有効
Lint/Loop begin ~ enduntilwhile 等でループ処理が正しく書かれているか 有効
Lint/MissingCopEnableDirective copを一部無効にする際、# rubocop:disable ... の後に # rubocop:enable ... が書かれて有効な状態に戻されているか 有効
Lint/MultipleComparison x < y < z のような、3つ以上の比較演算を行っている式が存在しないか
※ Rubyでは使用不可
有効
Lint/NestedMethodDefinition メソッド定義がネストされていないか 有効
Lint/NestedPercentLiteral %記法がネストされていないか 有効
Lint/NextWithoutAccumulator reduce ブロック内である条件でループ処理をスキップする際、累算器が next メソッド呼出しの引数に取られているか 有効
Lint/NonDeterministicRequireOrder Dir[...]Dir.glob(...) で取得したファイルを格納した配列がソートされているか
※ 上記メソッドで取得したファイルの順番はOSやファイルシステムに依存するため、アルファベット昇順とは限らないため
有効
Lint/NonLocalExitFromIterator 調査中 有効
Lint/NumberConversion 安全でない数値変換が行われていないか
to_ito_fto_c メソッドが使われていないか
Integer()Float()Complex() が使われているか
無効
Lint/OrderedMagicComments マジックコメントが正しい順序で書かれており、#! がマジックコメントの先頭に書かれているか 有効
Lint/ParenthesesAsGroupedExpression 引数ありのメソッド呼出しと ( の間に半角スペースが存在しないか 有効
Lint/PercentStringArray 文字列配列の %w 記法において、クオーテーションやコンマが使われていないか 有効
Lint/PercentSymbolArray シンボル配列の %i 記法において、コロン(:)やコンマが使われていないか 有効
Lint/RandOne rand(1) が呼び出されていないか 有効
Lint/RedundantCopDisableDirective 省略できる rubocop:disable コメントが存在しないか 有効
Lint/RedundantCopEnableDirective 省略できる rubocop:enable コメントが存在しないか 有効
Lint/RedundantRequireStatement 無意味な require が存在しないか 有効
Lint/RedundantSplatExpansion 配列展開の splat(*)が無意味に使われていないか 有効
Lint/RedundantStringCoercion 式展開で文字列変換(to_s)がされていないか 有効
Lint/RedundantWithIndex 無意味な with_index メソッドが使われていないか 有効
Lint/RedundantWithObject 無意味な with_object メソッドが使われていないか 有効
Lint/RegexpAsCondition 条件文の判断条件で match-current-line として正規表現が使われているか 有効
Lint/RequireParentheses 以下の条件に合致する場合に引数が () で囲まれているか
・少なくとも一つ以上の引数があるメソッド呼出しが並列されている
・並列されているメソッド呼出しの引数がいずれも () で囲まれていない
・並列されている最後のメソッド呼出しの前に &&, || がある
有効
Lint/RescueException 例外処理における rescue のブロックの中で、最上位の Exception クラスを指定していないか 有効
Lint/RescueType 例外が発生した時に rescue の引数が TypeError を発生させるものではないか 有効
Lint/ReturnInVoidContext return メソッドの引数の値に関して、それが無視される文脈(initializer やセッター)で引数に取られていないか 有効
Lint/SafeNavigationChain NoMethodError の例外が発生しないようにnilガードが適切にチェーンされているか 有効
Lint/SafeNavigationConsistency &&, || などの論理演算子でnilガードを使う場合において、両辺で同じオブジェクトを返す書き方がされているか
※ デフォルトは AllowedMethods: [present?, blank?, presence, try, try!]
有効
Lint/SafeNavigationWithEmpty 条件式の中でnilガードの後に empty? メソッドがチェーンされていないか
nil.empty? => NoMethodError
nil&.empty? => nil
有効
Lint/ScriptPermission #! から始まるマジックコメントが一行目に書かれているファイルが実行権限を有しているか 有効
Lint/SendWithMixinArgument モジュールのミックスインにおいて、プライベートメソッド呼出しの sendpublic_send__send__ が使われていないか
※ Ruby2.0まで includeprepend はプライベートメソッドであったが、Ruby2.1以降はパブリックメソッドである。extend は元々パブリックメソッドである。
有効
Lint/ShadowedArgument 引数のシャドーイングがなされていないか
※ デフォルトは IgnoreImplicitReferences: false
有効
Lint/ShadowedException 例外処理において、抽象度の高い例外クラスが具体性の高い例外クラスに先んじて例外を補足していないか 有効
Lint/ShadowingOuterLocalVariable ブロック引数がスコープ外のローカル変数と同名であることによってシャドーイングがなされていないか 有効
Lint/SuppressedException rescue ブロック内に処理が書かれているか
※ デフォルトは AllowComments: false
有効
Lint/Syntax 厳密にはcopではなく、診断情報やエラーをRubocopの規約違反に組み入れ直すメソッド群を提供する 有効
Lint/ToJSON to_json メソッドがオプション引数を取っているか 有効
Lint/UnderscorePrefixedVariableName アンダースコア付ブロック変数が内部処理に使われていないか
※ デフォルトは AllowKeywordBlockArguments: false
有効
Lint/UnifiedInteger FixnumBignum 等の非推奨の定数が使われていないか 有効
Lint/UnreachableCode return でメソッドを抜けた後に処理されないロジックが存在しないか 有効
Lint/UnusedBlockArgument 利用されていないブロック引数が存在しないか
※ デフォルトは IgnoreEmptyBlocks: trueAllowUnusedKeywordArguments: false
有効
Lint/UnusedMethodArgument 利用されていないメソッド引数が存在しないか
※ デフォルトは AllowUnusedKeywordArguments: falseIgnoreEmptyMethods: trueIgnoreNotImplementedMethods: true
有効
Lint/UriEscapeUnescape 非推奨のメソッドがユースケースに応じて推奨のメソッドに代替出来るか
URI.escape => CGI.escapeURI.encode_www_formURI.encode_www_form_component
URI.unescape => CGI.unescapeURI.decode_www_formURI.decode_www_form_component
有効
Lint/UriRegexp URI.regexp が使われていないか
URI::DEFAULT_PARSER.make_regexp が使われているか
有効
Lint/UselessAccessModifier 無意味に publicprivate などのアクセサが使われていないか 有効
Lint/UselessAssignment 使われていないローカル変数が存在しないか 有効
Lint/UselessComparison 同一のオブジェクトが比較演算子の両辺で使われていないか 有効
Lint/UselessElseWithoutRescue 例外処理において、begin ~ end の中で rescue 句なしで else が使われていないか 有効
Lint/UselessSetterCall セッターメソッドの中で、ローカル変数の値が最終行で返されているか
※ ローカルスコープ外からアクセス出来る値をローカル変数が参照するエッジケースが存在する。copには検出されず、誤判定が生じる可能性がある。
有効
Lint/Void 調査中
※ デフォルトは CheckForMethodsWithNoSideEffects: false
有効

Metrics

Cop名 解析対象 デフォルト
Metrics/AbcSize 算出されたABC(代入式、メソッド呼出し、条件式)の大きさが最大値を超えていないか
※ デフォルトは Max: 15
有効
Metrics/BlockLength ブロックの行数が最大値を超えていないか
※ デフォルトは以下の通り
CountComments: false
Max: 25
ExcludedMethods: [refine]
Exclude: [**/*.gemspec]
有効
Metrics/BlockNesting 条件式や繰返し処理等のブロック内の入れ子の数が最大値を超えていないか
※ デフォルトは以下の通り
CountBlocks: false
Max: 3
有効
Metrics/ClassLength クラスあたりの行数が最大値を超えていないか
※ デフォルトは以下の通り
CountComments: false
Max: 100
有効
Metrics/CyclomaticComplexity 循環的複雑度の値が最大値を超えていないか
※ デフォルトは Max: 6
有効
Metrics/MethodLength メソッド定義の行数が最大値を超えていないか
※ デフォルトは以下の通り
CountComments: false
Max: 10
ExcludedMethods: []
有効
Metrics/ModuleLength モジュールあたりの行数が最大値を超えていないか
※ デフォルトは以下の通り
CountComments: false
Max: 100
有効
Metrics/ParameterLists メソッド定義のパラメーターの数が最大値を超えていないか
※ デフォルトは以下の通り
Max: 5
CountKeywordArgs: true
有効
Metrics/PerceivedComplexity コードの複雑度が最大値を超えていないか
※ デフォルトは Max: 7
有効

Migration

Cop名 解析対象 デフォルト
Migration/DepartmentName rubocop:disable内のコメントでcop名が区分名を伴っているか 有効

Naming

Cop名 解析対象 デフォルト
Naming/AccessorMethodName アクセサのメソッド名が適切に命名されているか 有効
Naming/AsciiIdentifiers メソッド名や変数名などの識別名に非ASCII文字が使われていないか 有効
Naming/BinaryOperatorParameterName 二項演算子メソッド定義のパラメーターの引数名が other になっているか 有効
Naming/BlockParameterName ブロックパラメーターは意味のある命名がされているか
※ デフォルトは以下の通り
MinNameLength: 1
AllowNamesEndingInNumbers: true
AllowedNames: []
ForbiddenNames: []
有効
Naming/ClassAndModuleCamelCase クラス名とモジュール名がキャメルケースで統一されているか 有効
Naming/ConstantName 定数はスネークケースかつ大文字のみで構成されているか 有効
Naming/FileName ファイル名はスネークケースで統一されているか
※ デフォルトは以下の通り
Exclude: []
ExpectMatchingDefinition: false
Regex: <none>
IgnoreExecutableScripts: true
AllowedAcronyms: [CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS]
有効
Naming/HeredocDelimiterCase ヒアドキュメントの識別子は大文字のみで構成されているか
※ デフォルトは EnforcedStyle: uppercase
有効
Naming/HeredocDelimiterNaming ヒアドキュメントの識別子は意味のある命名がされているか
※ デフォルトは ForbiddenDelimiters:(?-mix:(^ 。また、デフォルトで END と EO*` の使用は不許可。
有効
Naming/MemoizedInstanceVariableName メモ化インスタンスメソッド名とスコープ内のインスタンス変数名が一致しているか
※ デフォルトは EnforcedStyleForLeadingUnderscores: disallowed
有効
Naming/MethodName メソッド名が決められた形式(スネークケース/キャメルケース)で命名されているか
※ デフォルトは EnforcedStyle: snake_case
有効
Naming/MethodParameterName メソッド定義のパラメーターは意味のある命名がされているか
※ デフォルトは以下の通り
MinNameLength: 3
AllowNamesEndingInNumbers: true
AllowedNames: [io, id, to, by, on, in, at, ip, db, os, pp]
ForbiddenNames: []
有効
Naming/PredicateName true/false を返す叙述メソッドが適切な命名をされているか
※ デフォルトは以下の通り
NamePrefix: [is_, has_, have_]
ForbiddenPrefixes: [is_, has_, have_]
AllowedMethods: [is_a?]
MethodDefinitionMacros: [define_method, define_singleton_method]
Exclude: [spec/**/*]
有効
Naming/RescuedExceptionsVariableName 例外処理において、例外を代入する変数が適切な命名がされているか
※ デフォルトは PreferredName: e
有効
Naming/VariableName 変数名が決められた形式(スネークケース/キャメルケース)で命名されているか
※ デフォルトは EnforcedStyle: snake_case
有効
Naming/VariableNumber アラビア数字を含む変数名における書式が正しいか
※ デフォルトは EnforcedStyle: normalcase
有効

Security

Cop名 検査対象 デフォルト
Security/Eval 非推奨の Kernel#eval や Binding#eval が使われていないか 有効
Security/JSONLoad 潜在的なセキュリティ問題を孕む JSON クラスメソッド が使われていないか 有効
Security/MarshalLoad 信頼していないソースコードを読み込んだ際に遠隔でのコード実行に繋がる潜在的なセキュリティ問題を孕む Marshal クラスメソッド が使われていないか 有効
Security/Open Kernel#open が危険な使われ方をされていないか
※ Kernel#open はファイルアクセスだけでなく、パイプシンボル(|)を前に置くことでプロセス呼出しも可能にする。よって、引数に取る変数によってな重大なセキュリティリスクを孕む
※ File.openIO.popenURI#open を使う方が安全である
有効
Security/YAMLLoad 信頼していないソースコードを読み込んだ際に遠隔でのコード実行に繋がる潜在的なセキュリティ問題を孕む YAML クラスメソッド が使われていないか 有効

Style

作業中

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gem install mysql2エラー(Error installing mysql2)

mysql2をインストールした際エラーが出たため、情報共有します。

インストールコマンド

gem install mysql2

エラー内容

Building native extensions. This could take a while...
ERROR:  Error installing mysql2:
    ERROR: Failed to build gem native extension.

    current directory: /Users/aki/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/ext/mysql2
/Users/aki/.rbenv/versions/2.7.0/bin/ruby -I /Users/aki/.rbenv/versions/2.7.0/lib/ruby/2.7.0 -r ./siteconf20200324-90394-m50btx.rb extconf.rb
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
-----
Using mysql_config at /usr/local/bin/mysql_config
-----
checking for mysql.h... yes
checking for errmsg.h... yes
checking for SSL_MODE_DISABLED in mysql.h... yes
checking for SSL_MODE_PREFERRED in mysql.h... yes
checking for SSL_MODE_REQUIRED in mysql.h... yes
checking for SSL_MODE_VERIFY_CA in mysql.h... yes
checking for SSL_MODE_VERIFY_IDENTITY in mysql.h... yes
checking for MYSQL.net.vio in mysql.h... yes
checking for MYSQL.net.pvio in mysql.h... no
checking for MYSQL_ENABLE_CLEARTEXT_PLUGIN in mysql.h... yes
checking for SERVER_QUERY_NO_GOOD_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_NO_INDEX_USED in mysql.h... yes
checking for SERVER_QUERY_WAS_SLOW in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_ON in mysql.h... yes
checking for MYSQL_OPTION_MULTI_STATEMENTS_OFF in mysql.h... yes
checking for my_bool in mysql.h... no
-----
Don't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load
-----
-----
Setting libpath to /usr/local/Cellar/mysql/8.0.19/lib
-----
creating Makefile

current directory: /Users/aki/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/ext/mysql2
make "DESTDIR=" clean

current directory: /Users/aki/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/ext/mysql2
make "DESTDIR="
compiling client.c
client.c:787:14: warning: incompatible pointer types passing 'VALUE (void *)' (aka 'unsigned long (void *)') to parameter of type 'VALUE (*)(VALUE)' (aka 'unsigned long (*)(unsigned long)') [-Wincompatible-pointer-types]
  rb_rescue2(do_send_query, (VALUE)&args, disconnect_and_raise, self, rb_eException, (VALUE)0);
             ^~~~~~~~~~~~~
/Users/aki/.rbenv/versions/2.7.0/include/ruby-2.7.0/ruby/ruby.h:1988:25: note: passing argument to parameter here
VALUE rb_rescue2(VALUE(*)(VALUE),VALUE,VALUE(*)(VALUE,VALUE),VALUE,...);
                        ^
client.c:795:16: warning: incompatible pointer types passing 'VALUE (void *)' (aka 'unsigned long (void *)') to parameter of type 'VALUE (*)(VALUE)' (aka 'unsigned long (*)(unsigned long)') [-Wincompatible-pointer-types]
    rb_rescue2(do_query, (VALUE)&async_args, disconnect_and_raise, self, rb_eException, (VALUE)0);
               ^~~~~~~~
/Users/aki/.rbenv/versions/2.7.0/include/ruby-2.7.0/ruby/ruby.h:1988:25: note: passing argument to parameter here
VALUE rb_rescue2(VALUE(*)(VALUE),VALUE,VALUE(*)(VALUE,VALUE),VALUE,...);
                        ^
2 warnings generated.
compiling infile.c
compiling mysql2_ext.c
compiling result.c
compiling statement.c
linking shared-object mysql2/mysql2.bundle
ld: library not found for -lssl
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [mysql2.bundle] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/aki/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3 for inspection.
Results logged to /Users/aki/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/extensions/x86_64-darwin-19/2.7.0/mysql2-0.5.3/gem_make.out

原因

どうやらsslが見つからないとのこと。

library not found for -lssl

とりあえずインストールはされていたため、アップグレード。

brew upgrade openssl

すでにインストールされていた。

Updating Homebrew...
Warning: openssl 1.1.1d already installed

gem installにオプションを付けインストールしてみることにしたら実行できた!

gem install mysql2 -v '0.5.2' -- --with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include

まとめ

SSLのエラーがgithubでも起こったため、一度集中的に調べて記事にしておこうと思います。

参考

もはやこのページの通り実行しただけです笑
githubページ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 投稿機能

Railsで投稿機能の作成です:point_up:
部分的な備忘録ですので分かりづらいと思います

railsアプリケーション作成

$ rails _5.2.3_ new sample_boad -d mysql

Gem追加

bundle installとbundle updateを実行

gem 'pry-rails'
gem 'compass-rails', '3.1.0'
gem 'sprockets', '3.7.2'

データベース作成

$ rake db:create

モデル作成

$ rails g model tweet

テーブル作成

マイグレーションファイルはテーブルの設計図のようなものです。
t.の後に続いている記述は、どんなデータが入るのかを示す型です。

srting 文字(少なめ)
text 文字(多め)
integer 数字
などがあります。

timespasはcreated_at(作成時間) や updated_at(更新時間) を定義する時に使用します。
:nameや:textはカラム名です。

この場合、nameカラム(ユーザー名)をstring型(ユーザー名は文字少なめ)、textカラム(投稿された文章)をtext型(文章だから文字多め)、imageカラム(画像)をstring型としています。

2020XXXXXXXXXXXX_create_tweets.rb
class CreateTweets < ActiveRecord::Migration[5.2]
  def change
    create_table :tweets do |t|
      t.string :name
      t.text   :text
      t.string   :image
      t.timestamps null: true
    end
  end
end

rake db:migrateでマイグレーションファイルを実行

ルーティング

投稿一覧を表示するindexアクション 投稿画面を作成するためにnewアクション 投稿された内容を保存するためにcreateアクションを使います。

route.rb
Rails.application.routes.draw do
  resources :tweets only: [:index,:new,:create]
end

コントローラー

private以下はストロングパラメーターです。
ビューでフォームに入力された情報は、コントローラにキーと一緒にパラメーターとして送られます。
ストロングパラメーターは、指定したキーを持つパラメーターのみを受け取るようにするものです。
不正な情報を送信しようとしたときに、ストロングパラメーターを設定しておくと、不正な情報を受け取らずにすみます。

$ rails g controller tweets
tweets.controller.rb
class TweetsController < ApplicationController

  def index
    @tweets = Tweet.order("created_at DESC").page(params[:page]).per(5)
  end

  def new
  end

  def create
    Tweet.create(tweet_params)
  end

  private
  def tweet_params
    params.permit(:name,:image,:text)
  end
end

Tweet.order("created_at DESC")は、投稿された内容を降順に並び替えて表示させています。
つまり、新しい投稿ほど上に表示されます。
page(params[:page]).per(5)はページネーションです。説明は省略します。Gemはkaminariを使用しています。

ビュー作成

投稿一覧
index.html
<html>
<head>
  <title> Sample boad</title>
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
    <%= csrf_meta_tags %>
</head>

<body>
<header class="header">
  <h1>掲示板</h1>
  <%= link_to "投稿する", "/tweets/new" ,class: "send_btn"%>
</header>
<div class="comment">
  <% @tweets.each do |tweet|%>
  <div class="comment-list">
    <div class="title">
      <%= tweet.title%>
    </div>
    <div class="comments">
      <%= tweet.text %>
    </div>
  </div>
   <% end %>
</div>
<%= paginate(@tweets)%>
</div>
</body>
</html>
投稿完了画面
create.html
<div class="contents row">
  <div class="succes">
    <h3>
    投稿が完了しました
    </h3>
    <a class="btn" href="/tweets">投稿一覧へ戻る</a>
  </div>
</div>
投稿画面

7〜9行目に:name :image :textの記述があります。
これが、コントローラーに送信されるキーを表しています。ユーザーに入力された情報はキーと一緒にパラメーターとして送られます。
コントローラーでストロングパラメーターを使っているので、:userなど指定されていないキーは受け取られません。

new.html
<div class="contents row">
  <div class="container">
    <%= form_with(model: @tweet, local: true) do |form| %>
      <h3>
        投稿する
      </h3>
      <%= form.text_field :name, placeholder: "Nickname" %>
      <%= form.text_field :image, placeholder: "Image Url" %>
      <%= form.text_area :text, placeholder: "text", rows: "10" %>
      <%= form.submit "SEND" %>
    <% end %>
  </div>
</div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでRSpecを追加しようとした時に発生したエラー「Could not find diff-lcs-1.3 in any of the sources Run `bundle install` to install missing gems.」の対処法

Gemfile
group :development, :test do
  gem 'rspec-rails'
end

GemfileにRSpecを追加して、以下のコマンドを実行したらエラーが発生した。

エラーメッセージ

$ bundle install
・・・
$ rails generate rspec:install
Could not find diff-lcs-1.3 in any of the sources
Run `bundle install` to install missing gems.

解決方法

$ ps axu | grep spring
maiamea              53324   0.1  0.1  4336604   9524   ??  Ss    4:41PM   0:00.92 spring app    | api-project | started 5 mins ago | development mode      
maiamea              36768   0.0  0.0  4366272    748 s002  S+   月04PM   0:00.58 spring server | api-project | started 24 hours ago   
maiamea              53389   0.0  0.0  4286728    716 s003  S+    4:46PM   0:00.00 grep spring
$ spring stop
Spring stopped.
$ rails generate rspec:install
Running via Spring preloader in process 53437
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

原因

rails server を高速で起動させるSpringが悪さをしていた。
Springが起動していたことによりGemfileの変更が反映されなかった。

Gemfileに変更を加えた場合は、まず rails server を止め、
次にSpringをstopさせる。
その後 rails server を起動させる必要があった。

Springとは?

Railsアプリケーションのプリローダーのgem (Rubyのライブラリ)
Railsアプリケーションをバックグラウンドで走らせたままにしておくことにより、2回目以降のコマンド実行時の待ち時間が減り開発効率が上がる。

プリローダー:preloader (pre + load = 前もってロードしておく)

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

線分の交差判定と交点特定

はじめに

二つの折れ線グラフがある時、そのグラフの交点を探したいことがあります。こんな感じです。

intersection.png

典型的には異なるサイズのBinder比の交点を見つける時とかに必要です。で、そのためには「二つの線分の交差判定」をするコードと「交差する二つの線分の交点」を求めるコードが必要になるのですが、なんかこれ、必要になる度に毎回書いている気がするので、以下では交差判定と交点を求めるアルゴリズムを真面目に導出して、それをコードに落としてCC0で公開しました。

https://github.com/kaityo256/find_intersection

なお、線分直上に点があったり、二つの線分が平行だったりといった条件は考慮していないので注意。また、やや冗長に書いているので、高速化が必要な場合は適宜修正してください。

二つの線分の交差判定

4つの点、$P_1$, $P_2$, $P_3$, $P_4$があるとしましょう。点$P_i$の座標を$(x_i, y_i)$とします。この時、線分$P_1 P_2$と$P_3 P_4$は交点を持つか、交点を持つならその交点の座標を知りたいとします。

まず、線分$P_1 P_2$を通る直線の式を求めましょう。この直線の傾き$a$は

a = \frac{y_2 - y_1}{x_2 - x_1}

です。これが点$(x_1, y_1)$を通るので、

y - y_1 = a (x - x_1)

が求める直線の式です。分母を払って整理すると

(x_2 - x_1)(y - y_1) - (y_2 - y_1) (x-x_1) = 0

となります。さて、この式の左辺を$f_{12}(x, y)$としましょう。点$P_3$と点$P_4$が、この直線を挟んで反対側にあるためには、$f_{12}(x_3, y_3)$と$f_{12}(x_4, y_4)$が異符号でなければなりません。

つまり、

f_{12}(x_3, y_3) f_{12}(x_4, y_4) < 0

が成り立つ必要があります。

fig1.png

同様に、線分$P_3 P_4$が作る直線の式を$f_{34}(x,y) = 0$とすると、

f_{34}(x_1, y_1) f_{34}(x_2, y_2) < 0

が成り立つ必要があります。もし線分が交差していない場合は、同符号になる組み合わせが出てきます。例えば以下は、線分$P_3 P_4$が作る直線は線分$P_1 P_2$と交点を持つのに対し、線分$P_1 P_2$が作る直線は線分$P_3 P_4$とは交点を持たない例です。点$P_3$と$P_4$が直線の同じ側にあるため、$f_{12}(x_3, y_3)$と$f_{12}(x_4, y_4)$が同符号になります。

fig2.png

さて、上記の判定アルゴリズムをそのままコードに落としましょう。まずは点を表すStructを作るんですかね。

Point = Struct.new(:x, :y)

線分$P_1 P_2$が作る直線を$f_{12}(x, y) = 0$として、そこに点$P_3$を代入した時の値を返す関数をf(p1, p2, p3)として定義しましょう。

def f(p1, p2, p3)
  (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)
end

これを使うと、線分$P_1 P_2$と$P_3 P_4$が交差しているかの判定は以下のように書けます。

def intersect?(p1, p2, p3, p4)
  t1 = f(p1, p2, p3)
  t2 = f(p1, p2, p4)
  t3 = f(p3, p4, p1)
  t4 = f(p3, p4, p2)
  t1 * t2 < 0.0 and t3 * t4 < 0.0
end

二つの線分の交点

二つの線分$P_1 P_2$と$P_3 P_4$が交差していることがわかったとします。次に、交点を求めましょう。

線分$P_1 P_2$上の点$X$が、線分を$t$対$1-t$に分割したとします$(0 < t < 1)$。すると、点$X$の座標は

t
\begin{pmatrix}
x_1 \\
y_1
\end{pmatrix}
+
(1-t)
\begin{pmatrix}
x_2 \\
y_2
\end{pmatrix}

と書けます。また、$X$は線分$P_3 P_4$上の点でもあるので、パラメータ$s (0<s<1)$を用いて

s
\begin{pmatrix}
x_3 \\
y_3
\end{pmatrix}
+ 
(1-s)
\begin{pmatrix}
x_4 \\
y_4
\end{pmatrix}

とも書けます。

fig3.png

したがって、

t
\begin{pmatrix}
x_1 \\
y_1
\end{pmatrix}
+
(1-t)
\begin{pmatrix}
x_2 \\
y_2
\end{pmatrix}
=
s
\begin{pmatrix}
x_3 \\
y_3
\end{pmatrix}
+ 
(1-s)
\begin{pmatrix}
x_4 \\
y_4
\end{pmatrix}

です。これを$t, s$について整理すると、

\begin{pmatrix}
x_1-x_2 \\
y_1-y_2
\end{pmatrix}
t
-
\begin{pmatrix}
x_3-x_4 \\
y_3-y_4
\end{pmatrix}
s
= 
\begin{pmatrix}
x_4-x_2 \\
y_4-y_2
\end{pmatrix}

となります。これを、

A
\begin{pmatrix}
t \\
s
\end{pmatrix}
=
b

という連立一次方程式の形に書き直すと、

\begin{pmatrix}
x_1-x_2 & x_4 - x_3 \\
y_1-y_2 & y_4 - y_3
\end{pmatrix}
\begin{pmatrix}
t \\
s
\end{pmatrix}
=
\begin{pmatrix}
x_4-x_2 \\
y_4-y_2
\end{pmatrix}

です。$t, s$について解く(両辺に$A^{-1}$をかける)と、

\begin{pmatrix}
t \\
s
\end{pmatrix}
=
\frac{1}{|A|}
\begin{pmatrix}
y_4-y_3 & x_3 - x_4 \\
y_2-y_1 & x_4 - x_3
\end{pmatrix}
\begin{pmatrix}
x_4-x_2 \\
y_4-y_2
\end{pmatrix}

となります。ただし、$|A|$は$A$の行列式で、

|A| = (x_1-x_2)(y_4-y_3) - (x_4-x_3)(y_1-y_2)

です。以上から、

t = \frac{(y_4-y_3)(x_4-x_2) + (x_3-x_4) (y_4-y_2)}{|A|}

です。点$X$の座標は

\begin{pmatrix}
t x_1 + (1-t) x_2 \\
t y_1 + (1-t) y_2 \\
\end{pmatrix}

と求まりました。

上記をそのままコードに落としましょう。

def intersection(p1, p2, p3, p4)
  det = (p1.x - p2.x) * (p4.y - p3.y) - (p4.x - p3.x) * (p1.y - p2.y)
  t = ((p4.y - p3.y) * (p4.x - p2.x) + (p3.x - p4.x) * (p4.y - p2.y)) / det
  x = t * p1.x + (1.0 - t) * p2.x
  y = t * p1.y + (1.0 - t) * p2.y
  return x, y
end

まぁ、そのままですね。

折れ線グラフの交点

なんかデータがこんな感じで与えられているとしましょう。

-0.00929403432032172 10.06733812455202
1.059235593057012 11.146562883395475
2.064831028312785 14.24963231445969
2.968107972293056 18.723725728939
4.0981278592926005 26.724011540146748
5.099077600475496 36.01584720448775
5.960053040855195 45.56438755538092
6.9441326742759495 58.20967188095562
8.050356615496234 74.89595746961379
9.02282603720683 91.33504895073239

こんなファイルを二つ読み込んで、交点を返す関数find_intersectionはこんな感じに書けるでしょう。

def read_file(filename)
  File.read(filename).split(/\n/).map do |n|
    x, y = n.split(/\s+/)
    Point.new(x.to_f, y.to_f)
  end
end

def find_intersection(file1, file2)
  puts "reading #{file1}"
  data1 = read_file(file1)
  puts "reading #{file2}"
  data2 = read_file(file2)
  (data1.size - 1).times do |i|
    p1 = data1[i]
    p2 = data1[i + 1]
    (data2.size - 1).times do |j|
      p3 = data2[j]
      p4 = data2[j + 1]
      if intersect?(p1, p2, p3, p4)
        return intersection(p1, p2, p3, p4)
      end
    end
  end
end

要するにすべての線分の組み合わせについて交差判定をして、最初に見つけた組の交点を返す関数です。$O(N^2)$になってますが、点の数が多くなければ問題ないと思います。また、複数の交点を持つ場合は想定していません。

まとめ

線分の交差判定と交点を求めるアルゴリズムを導出して、コードに落としました。ネットにも似たようなコードが落ちているんですが、ライセンスが不明瞭だったりして、自分の公開コードに埋め込みづらかったりするんですよね。

上記のコードはCC0で公開しているので、「線分 交点」「線分 交差」等のキーワードでググってこの記事にたどり着いた人は好きに使ってください。コードはCC0なので好きに修正してくださってかまいませんが、それを公開する時には適切なライセンスを付与してもらえるとうれしいなぁ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails 新規アプリ作成

はじめに

今まで学習した知識を使って、オリジナルアプリを作るカリキュラムの段階まできました。
自身のアウトプット、皆さんへの共有の目的で、これから個人アプリが完成するまでの流れを投稿していきます。

Railsで新規アプリを作成する

ターミナルにて

$ rails _5.2.3_ new 〇〇 -d mysql

を、実行する。

railsのあとの『5.2.3』はrailsのバーション指定。
〇〇は作成したいアプリ名。
-dはデータベースの意味。
私の場合、データベースはmysqlを使用したかったので、-dで指定しました。

作成するアプリのディレクトリへ入り、ターミナルにて

rails db:create

を実行してデータベースを作る。

ここでひとまずアプリの大枠は完成です。
試しに

ターミナルにて

rails s

でサーバーを立ち上げ、localhost:3000へアクセスすると

rails_welcome.png

こちらの画像が表示され、正常にアプリが作られていることがわかります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

deviseでログイン機能を実装したものの、データベースへ書き込めない問題

起きた問題

deviseにてユーザーのログイン機能やユーザー編集機能などを実装したが、ユーザー編集機能のページedit.html.erbにて入力した内容がうまくデータベースに反映されない。

具体的にいうとnameのカラムだけなぜかdbに反映されなかったので、調べてみた。

schema.rbを見てもちゃんとカラムが追加されている。。。

解決した方法とソース

まず、解決した方法は簡単で、application_controller.rbに以下のコードを追加するだけ。

application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    # 「登録時(sign_up)」に許可するパラメータを追加
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])

    # 「更新時(account_update)」に許可するパラメータを追加
    devise_parameter_sanitizer.permit(:account_update, keys: [:name])
  end
end

deviseではどうやらデフォルトでemail、password以外は許可しない設定になっているらしく、上記のようなコードを追加することで許可してあげないといけないようだった。

以下のURLはdeviseのGithub READMEから。

https://github.com/heartcombo/devise#strong-parameters

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

S3バケットのオブジェクト名を1000個以上取得する

公式リファレンスに記載があるように、list_objects_v2は1000個以上のS3オブジェクトを取得する際には面倒なnext_tokenの処理が必要になります。

そこで、1000個以上のS3オブジェクトを扱う際には、Aws::S3::Bucket#objectsを使うと、このようにシンプルに書けます。

list-objects.rb
require 'aws-sdk'
bucket = Aws::S3::Bucket.new('<バケット名>')
obj_array = bucket.objects.map(&:key)

obj_array.size  # => オブジェクト数
obj_array.first # => プレフィックス/オブジェクト名

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

S3バケットのオブジェクトを1000個以上取得する

公式リファレンスに記載があるように、list_objects_v2は1000個以上のS3オブジェクトを取得する際には面倒なnext_tokenの処理が必要になります。

そこで、1000個以上のS3オブジェクトを扱う際には、Aws::S3::Bucket#objectsを使うと、このようにシンプルに書けます。

list-objects.rb
require 'aws-sdk'
bucket = Aws::S3::Bucket.new('<バケット名>')
obj_array = bucket.objects.map(&:key)

obj_array.size  # => オブジェクト数
obj_array.first # => プレフィックス/オブジェクト名

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

既存RailsアプリにCircleCIを導入した手順

自作のRailsアプリにCircleCIを使ってCI/CDを導入しました
今回は、CircleCIとGitHubとの連携までを行います。

自動化したいこと

  • テスト
  • コード解析
  • デプロイ

ただ、今回は導入しか行いませんので、正直以上の3つは関係ありません。

CircleCIを採用した理由

  • 無料プランがついている
  • メジャーどころっぽい
  • 日本語の資料が多い(死活問題)

1. アカウント取得・連携

やることは以下の3つ。

  • CicleCIに登録
  • GitHubと連携(登録と同時にできる)
  • CircleCIを導入するアプリ(リポジトリ)を選ぶ

CircleCI公式(英語)でSignUpします。
基本的には画面の指示に従っていたら間違いないはずです。

英語じゃねえか... と心が折れかかりますが、私でもなんとかなったので多分大丈夫です。
少し時間が経っていて画面デザインが少し違いますが、次のブログは参考になります。
【CircleCI】CircleCI 2.0からはじめる個人での簡単なCI導入方法 - githubとの連携まで

CircleCIを導入するアプリを選んだら、ブランチを作って設定ファイルを自動で作ってあげるよ!と言ってくれますが、私は勉強も兼ねて自分で作りました。

2. 設定ファイルを作成する

CircleCIに登録後、表示されたconfig.ymlのサンプルをコピペします。

その前に、コピペ先を導入アプリのルートディレクトリにフォルダと設定ファイルを作成します。

$ cd ~/Rails_App
$ midir .circleci
$ touch .circleci/config.yml

作成したconfig.ymlを開いて(vimでもエディタでも)、設定サンプルをコピペします。

私のサンプルはこうなってました。

config.yml
version: 2.1
orbs:
  ruby: circleci/ruby@0.1.2 

jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.3-stretch-node
    executor: ruby/default
    steps:
      - checkout
      - run:
          name: Which bundler?
          command: bundle -v
      - ruby/bundle-install

試しにpushしてみます。

$ git add -A
$ git commit -m "CircleCIの設定ファイルを追加"
$ git push origin master
or
$ git push origin branch-name

開発ブランチなどで作業していた場合は、GitHubでプルリクエストします。

CircleCIのダッシュボードを確認し、pushに反応してアイコンがグルグルしていると思うので、待ちます。

PixelSnap 2020-03-24 at 09.46.19@2x.png

少し待つと(私の環境で3分)SUCCESSとなるので、
こうなったら、CircleCIとGitHubとの連携は完了です。

導入完了

これからは自動化したいことによって、.circleci/config.ymlを書き換えていきます。

Railsの場合は、公式が日本語ガイドを用意してくれています。
言語ガイド

CI/CDの概略やCircleCIの設定ファイルを優しく解説してくれている
ネット記事もありました。
「エンジニアのためのCI/CD再入門」連載一覧

ご参考までに。

最後までお読みいただきありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kinx 実現技術 - VM(Virtual Machine)

Virtual Machine

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。作ったものの紹介だけではなく実現のために使った技術を紹介していくのも貢献。その道の人には当たり前でも、そうでない人にも興味をもって貰えるかもしれない。

前回のテーマは Switch-Case、今回のテーマは VM (Virtual Machine)。

Virtual Machine

仮想機械。Kinx の場合、構文解析した AST から IR (Intermediate Representation) を構築、それを直接実行する。命令数は現時点で 190。include/ir.h に一覧がある。 一応、一通り第二オペランドの型がわかるものに関しては型ごとに命令を用意することで実行時コストを下げるようにはしてある。

出力例

今回は出力例から。以下のフィボナッチ数列のベンチマーク;

function fib(n) {
    if (n < 3) return n;
    return fib(n-2) + fib(n-1);
}

System.println("fib(34) = ", fib(34));

これをコンパイルしてみると、以下のようになる。

_startup:
  .L1
       0:   jmp                     .L2(2)
       1:   halt

_main1:
  .L2
       2:   enter                   37, vars(35), args(1)
       3:   pushf                   __anonymous_func58 => .L3(10)
       4:   call                    0
       5:   pop
       6:   pushf                   fib => .L460(d0a)
       7:   storevx                 $(0,33)
       8:   pushi                   34
       9:   callvl0                 $0(33), 1
       a:   pushs                   "fib(34) = "
       b:   pushvl0                 $0(1)
       c:   calls                   "println", 2
       d:   pop
       e:   ret                     null
       f:   halt

fib:
  .L460
     d0a:   enter                   23, vars(1), args(1)
  .L461
     d0b:   lt_v0i                  $0(0), 3
     d0c:   jz                      .L463(d0e)
  .L462
     d0d:   retvl0                  $0(0)
  .L463
     d0e:   pushvl0                 $0(0)
     d0f:   subi                    2
     d10:   callvl1                 $1(33), 1
     d11:   pushvl0                 $0(0)
     d12:   subi                    1
     d13:   callvl1                 $1(33), 1
     d14:   add
     d15:   ret
     d16:   halt

アドレスが飛んでいるのは、標準ライブラリの読み込みなど、スタートアップルーチンの表示を省略するようにしているからです(__anonymous_func58 のあたり)。

ダイレクト・スレッデッド・コード

gcc の場合、ラベルに対するジャンプ命令を生成できるので俗にいう ダイレクト・スレッデッド・コード が実現できる。残念ながら Visual Studio では実現できない。

まず、最初にラベルに対するアドレス用のテーブルを作成する。この時、命令コードの番号と配列位置を合わせておくことでアドレスを一発で引けるようにしておく。ちなみに、C のラベルは関数をまたげないので、関数内で閉じてないといけない。

static void *jumptable[] = {
    &&LBL_KX_HALT,
    &&LBL_KX_NOP,
    &&LBL_KX_DUP,
    &&LBL_KX_IMPORT,
    &&LBL_KX_ENTER,
    &&LBL_KX_CALL,
    &&LBL_KX_CALLV,
    &&LBL_KX_CALLVL0,
    ...
};

次に、命令を一通りスキャンして、命令に対するアドレスを設定する。

for (int i = 0; i < code_len; ++i) {
    kx_code_t *c = fixcode[i];
    c->gotolabel = jumptable[c->op];
    ...
}

準備が整っていれば、命令実行の最後で次の命令に移動して goto する。

LBL_KX_ENTER:
    ...
    cur = cur->next;
    goto *(cur->gotolabel);

この辺が、include/kxexec.h にマクロとして定義してある。マクロは gcc と Visual Studio での共通化用です。

ダイレクト・スレッディングに関しては、結構古い記事だが YARV Maniacs 【第 3 回】 命令ディスパッチの高速化 がやっぱり分かりやすいと思う。

スタック構造

スタックは演算で使用されるが、関数呼び出し時にフレームを作成する。フレームにはローカル変数を格納するバッファが用意されており、フレームが GC されない限り参照できる。関数呼び出し時のスタック構造は以下の通り。

[ 0] frame obj   .lex = previous lexical frame.
---------------------------------------------------------
[-1] return address
[-2] param count
[-3] function obj (.lex)
[-4] param 1
[-5] param 2
[  ] ...
[..] param n
[  ] ...
[  ] frame obj -- previous frame
---------------------------------------------------------

.lex はレキシカル・フレームへのポインタ。リンクリストの形でさかのぼることができる。

余談だが、ここで演算用のスタックをフレームごとに個別に持たせれば Fiber でスタック状態も復元できると想定しているのだが、そこまで頑張る必要があるかどうかよく分からない。自分自身は困っていない。というのも、スタック状態を復元して良くなる点といえば式の中に yield を書くといった以下のようなコードだが、こういうの使えなくても良い気がする。

var x = (yield 10)[0] + 50;
var y = func(yield a, ++a);

ちなみに現在の関数呼び出し時の引数評価は 後ろから だが、一般的にこれをあまり保証したくないので、上記のうちの関数呼び出しで使われる yielda の値はインクリメント後か前かは保証されないことになるだろう。

レキシカル変数

レキシカル変数は関数オブジェクト作成時にリストとして関数オブジェクト自体に設定される。関数呼び出しの際、関数オブジェクトに格納されていたレキシカルスコープへのポインタが、作成されたフレームに設定される。関数内で関数が定義されると、その時のフレームをレキシカル・フレームとして登録することで数珠つなぎの形で参照できるようになる、といった算段。

スタック上のイメージは、フレームごとにレキシカル参照が設定されている状態。

|↑| frame -> lex -> lex -> ...
|ス|  │
|タ|  │
|ッ|  ↓
|ク| frame -> lex -> lex -> ...
|↓|  │

尚、レキシカル参照されているフレームは GC で回収されないようにマークを付けなければならない。

スプレッド演算子(...

関数呼び出しの際のスプレッド演算子(例:func(...a)等)は、呼び出しの段階で個別のパラメータに展開される。その際、その数に合わせて param count の値が調整された形で格納される。

例えば以下のコード;

b = [1,2,3];
func(a, ...b);

これは実行時に以下のように展開されたのと同じ動作をする。

b = [1,2,3];
func(a, 1, 2, 3);

もちろんコンパイル時には決定できないので、実行時に展開される。

eval

eval() は、VM をネストさせるのが大変そうだったので、動的にコンパイルした結果を現在のコードの最後に追記し、同じ VM 上で単にジャンプするように実装している。こうしておくと例外発生時の扱いも単純になる。

その他

どうも Visual Studio は Switch-Case の Case 数がある閾値を超えると 最適化をやめてしまう 模様。ちゃんとした公式文書が発見できなかったので、このあたりに詳しい方がいたら教えてください。目に見えてコンパイル時間が短くなり、パフォーマンスが悪くなる。

それにしても最適化が有効な時の src/ir_exec.c の Visual Studioでのコンパイル時間が泣きたいほどに遅い。かといってココを最適化しとかないと実行時速度がやばことになる。(今は WSL 上でやっている)gcc だと全然苦にならないほどコンパイルが速い。それでもダイレクト・スレッデッド・コードのおかげか、gcc でコンパイルしたほうがパフォーマンスも良い。実行ファイルに依存性を持たせない意味で Windows 上でのビルドは Visual Studio がいいんだけどなー。どうにかならんかな。

今後

IR のセーブ・ロード機能を付けたいところ。命令ごとに使うフィールドは決まっているので、それをルール化して書き出せばよいし、同じように読めばよい。やることはわかっているのだが、時間が追い付いていないな。

おわりに

今回も時間を割いて読んでいただいてありがとうございます。最後はいつもの以下の定型フォーマットです。興味がありましたら とか いいね LGTM」 ボタンとか押してもらえるとモチベーションにつながります。どうぞよろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

git pull時に 【Your local changes to the following files would be overwritten by merge】とエラー

エラー内容

$ git pull origin masterをすると

error: Your local changes to the following files would be overwritten by merge:
config/routes.rb
Please commit your changes or stash them before you merge.

が出てくる。

原因

pullした内容と自分の編集した箇所(ここではconfig/routes.rb)が被っている。

解決策

pullした内容の箇所が自分も編集している所の為、mergeする前にcommitするかstashしてと言われる。

なので、今回はコミットを選択。

$ git add *
$ git commit -m "コミット名"
$ git push origin 自分の作業ブランチ

でpushし、Github上で
コンフリクトが起きなければ再度、

$ git pull origin master

とpullすればOK。
コンフリクトの場合はコンフリクト
内容を修正してから再度、pull。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails form_withでフォロー機能作成

はじめに

今回はフォロー機能を作成します。railsチュートリアルではform_forを利用していましたが、今回はform_withを利用して作成します。
(題材は自分のポートフォリオです。Userモデルはある前提で進めます。)

対象読者

railsチュートリアル終わったレベルくらいの人

作成の流れ

1.relationshipモデルの作成
2.各モデルの関連付け、フォロー機能のメゾット作成
3.対応するコントローラーの作成
4.form_withを利用したフォローボタン作成
5.jsファイルの作成
6.フォロー機能の流れ

今回のAjaxの流れ

①fomr_withで作成したフォロー/アンフォローボタンを押すとPost/deleteリクエストが送られる。

②対応するコントローラのcreate/destroyアクションを実行

③js.erbファイルをレンダリング

④Ajax対象範囲のフォロー/アンフォローボタンを非同期で切り替え

1.relationshipモデルの作成

relationshipモデルの中身はこんな感じです。中間テーブルを作成しますが別にフォローしているユーザーの情報を記録するようなテーブルは作りません。実際のフォローする際のテーブルの流れは
Userモデル→Follow_relationship→Userモデルと帰ってくる流れになります。
follower,followingと名前がついていますがどちらもUserのidが入ります。

カラム
id integer
follower_id integer
following_id integer

モデルを作成します。
それぞれのカラムに外部キー制約とindexをこの後つけるのでreferencesを付けて生成します。(referencesをつけると自動で〇〇_idの形にしてくれます。)

rails g model Follow_Relationship follower:references following:references

2.各モデルの関連付け、フォロー機能のメゾット作成

作成されたマイグレーションファイルを確認します。

db/migrate
class CreateFollowRelationships < ActiveRecord::Migration[5.1]
  def change
    create_table :follow_relationships do |t|
      t.references :follower, foreign_key: { to_table: :users }
      t.references :following, foreign_key: { to_table: :users }

      t.timestamps
    end
    add_index :follow_relationships, [:follower_id, :following_id], unique: true
  end
end

ここでforegin_key(外部キー)を設定しています。よく外部キーを設定する時に使う「foreign_key: true」としてしまうと存在しないfollowersデーブルを参照してしまうので「to_table: :users」として参照はusersテーブルのidであることを指定してます。
外部キーを設定することによりindexの追加と存在するuserのidのみをdbに保存するようになります。
また、unique: trueとすることにより、同じ組み合わせでデータを保存するのを防ぐようにしているので同じユーザーを2回フォローできなくしています。

各モデルの関連付けはこのような形になります。

app/model/follow_relationship.rb
belongs_to :follower, class_name: "User"
belongs_to :following, class_name: "User"

validates :follower_id, presence: true
validates :following_id, presence: true
app/model/user.rb
has_many :following_relationships,foreign_key: "follower_id", class_name: "FollowRelationship",  dependent: :destroy
has_many :followings, through: :following_relationships
has_many :follower_relationships,foreign_key: "following_id",class_name: "FollowRelationship", dependent: :destroy
has_many :followers, through: :follower_relationships

それぞれのオプションの意味は
foreign_key: "follower_id"  外部キーの名前を直接参照します。
※マイグレーションで設定した物はあくまでもUserのidがこのカラムに入りますと指定しただけなのでフォローしているユーザーを検索等するときはfollowingカラムを参照するよと指定しないといけません。
class_name:関連名(following_relationships)としていますがそのようなテーブルは存在しないので実際に参照するテーブルであるFollowRelationshipを指定してあげます。
dependent: :destroy Userが削除された時にrelationshipのuseridが削除されるようにしています。

has_many :followings, through: :following_relationships
とすると先ほど指定したfollowing_relationshipsを通してフォローしているユーザーのidを取得できるようになります。

フォロー関連のメゾットを作成します。

app/model/user.rb
#すでにフォロー済みであればture返す
  def following?(other_user)
    self.followings.include?(other_user)
  end

  #ユーザーをフォローする
  def follow(other_user)
    self.following_relationships.create(following_id: other_user.id)
  end

  #ユーザーのフォローを解除する
  def unfollow(other_user)
    self.following_relationships.find_by(following_id: other_user.id).destroy
  end

3.コントローラーの作成

まずはコントローラーを作成します。

rails g controller follow_relationships

:
続いてルーディングの設定です。フォロー/フォロワーの一覧ページ用のアクションはUserオブジェクト絡みの機能なのでコントローラー内に記載します。
そのために、resources: userにmemberを使用してルートを追加します。
memberを使用するとURL内にユーザーを識別するidが追加されます。
idは後ほどUserコントローラーの対応するアクションないで必要となるのでmemberでルートを追加しないといけません。(idを追加しないcollectionというメゾットもあります。)

confing.routes.rb
resources :users do
      member do
        get :following, :followers
      end
    end
resources :follow_relationships, only: [:create, :destroy]

一覧ページ用のアクションはこのようになります。(kaminariのページネーション機能を利用しています。)
※それぞれ対応するビューを作成してください。

app/controllers/users.rb
def followings
    @user =User.find(params[:id])
    @users =@user.followings.page(params[:page]).per(5)
    render 'show_followings'
  end

  def followers
    @user =User.find(params[:id])
    @users =@user.followers.page(params[:page]).per(5)
    render 'show_followers'
  end

follow_relationshipのコントローラーはこんな感じです。
followとunfollowが先ほどUser.rbに記載したメゾットになります。

app/controllers/follow_relationships.rb
def create
    @user =User.find(params[:follow_relationship][:following_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html {redirect_back(fallback_location: root_url)}
      format.js
    end
  end

  def destroy
    @user = User.find(params[:follow_relationship][:following_id])
    current_user.unfollow(@user)
    respond_to do |format|
      format.html {redirect_back(fallback_location: root_url)}
      format.js
    end
  end
end

respond_to do |format|はリクエストの種類によってフォロー/アンフォローした際にレンダリングするビューを指定しています。クライアント側の設定でjsが無効になっている場合にhtml側の処理を書いておかないとエラーが出るので注意してください。
[format.html]ではredirect_backメゾットで直前のページを表示、表示できなければroot_urlに戻しています。※redirect_toを使用してflashを表示するパターンも設定できます。
jsでリクエストがくれば[format.js]を通って、follow_relationshipのcreate.js.erbへ処理が向かいます。format.jsの後にrender先を指定しない場合は自動的にアクションに対応するjsファイルを読み込みます。この場合はこの後記載するcreate.js.erbが該当するファイルになります。

app/controllers/follow_relationships.rb
flashバージョン
def create
    @user =User.find(params[:follow_relationship][:following_id])
    current_user.follow(@user)
    respond_to do |format|
      format.html { redirect_to @user, flash: {success: 'フォローしました!'} }
      format.js
    end
  end

4.フォローボタン作成

コントローラーのアクションができたのフォローボタンを作成します。その前にフォロー一覧とフォロワー一覧ページを作成しておきます。今回はUserのshowページにリンクを用意してます。※フォロー機能には直接の関係はありません。

app/views/users/show_html
<%= link_to "フォロー(#{@user.followings.count})", followings_user_path(@user), class: "nav-link" %>

<%= link_to "フォロワー(#{@user.followers.count})", followers_user_path(@user), class: "nav-link" %>

フォローボタンも同じくshowページに配置しますがコードがグチャグチャにならないようにボタンのフォームはrenderしてます。

app/views/users/show_html
<% if logged_in? && @user != current_user%>
  <div id="follow_form">
    <% if current_user.following?(@user) %>
      <%= render "unfollow" %>
    <% else %>
      <%= render "follow" %>
    <% end %>
  </div>
<% end %>

if logged_in? && @user != current_user
→ログイン済みであることと、このshowページが自分以外のユーザーのページであることを確認しています。&&はAND条件を表すので全ての条件を満たさないとボタンが
表示されません。!=は等しくないときtureとなります。
if current_user.following?(@user)
→ログイン中のユーザーがすでにフォロー済みであればunfollowをまだフォローしていなければfollowボタンを表示するようにしています。
次は実際のボタンの中身です。

app/views/users/follow_html
<%= form_with(model: current_user.following_relationships.build) do |f| %>
  <%= f.hidden_field :following_id, value: @user.id %>
  <%= f.submit "フォローする", class: "btn btn-outline-secondary" %>
<% end %>

from_withはデフォルトでAjax通信をするようになっています。
modelにはモデルクラスのインスタンス(@userとか)を渡す必要があるので、空のfollow_relationshipインスタンスを作成してpostリクエストを動作させ、follow_relationshipのコントローラーのcreateアクションに渡します。(userを作成するときにコントローラーのアクションでUser.newをする意味と同じです。多分)
f.hidden_fieldでfollowing_idにユーザーidを入れるようにしています。
※クライアント側には見えずに、ボタンを押した時にパラメーターに入ります。

app/views/users/unfollow_html
<%= form_with(model: current_user.following_relationships.find_by(following_id: @user.id),method: :delete) do |f| %>
  <%= f.hidden_field :following_id %>  
  <%= f.submit "フォロー", class: "btn btn-outline-secondary" %>
<% end %>

フォロー解除ボタンはフォローボタンとは違ってdeleteリクエストを指定してdestroyアクションを動作させています。

5.jsファイルの作成

フォームからのPostリクエストをcreateアクションで処理したあとはcreate.js.erbにたどり着きます。そこでAjaxでレンダリングする部分(今回の場合はボタン)を指定します。create.js.erbにたどり着くのはcreateアクション後なのでレンダリングするのはフォロー解除ボタンになります。

app/views/follow_relationships/create.js.erb
$("#follow_form").html("<%= j(render("users/unfollow")) %>");
app/views/follow_relationships/destroy.js.erb
$("#follow_form").html("<%= j(render("users/follow")) %>");

それぞれ対応するフォームを読み込みます。follow_formはshowページに記載したcssのidです。
以上でフォロー機能が動作するようになりました。
最後にフォロー機能の流れだけまとめで記載しておきます。

6.フォロー機能の流れ
実際にフォローボタンを押すとこのような動きをサーバーのログから確認できます。
※流れを追うだけなので一部省略してます。

Started POST "/follow_relationships"→フォローボタンが押される
Processing by FollowRelationshipsController#create as JS
Parameters:"follow_relationship"=>{"following_id"=>"3"}→パラメータにfollow_relationshipをキーとしたフォロー対象のユーザーidが入ります。
コントローラーのアクション完了!
Rendering follow_relationships/create.js.erb→アクション後にjsのファイル読み込み。
Rendered users/_unfollow.html.erb (2.1ms)
Rendered follow_relationships/create.js.erb (3.2ms)

以上となります。
かなり長くなってしまいましたが、最後までお読みいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む