20190719のRubyに関する記事は16件です。

バーチャル脱出ゲーム:バリデーションからの脱出(問題編)

Rubyのサンドボックスを作って、evalするBotを作ったという記事をこの間読んだんですよ

この記事自体は大分前のものですが、最新版を突破できないかと試行錯誤している時に「もしバリデーションでサンドボックスを作ろうとしたら」とか考えまして

まあ、軽く考えてブラックリスト方式はもちろん、ホワイトリストでもまず無理だと思いましたが

今回の問題は、その際に軽く考えた内容をベースにしたものです

問題

quiz.rb
class Tester < Ripper::Filter
  def on_ident(name, *)
    raise
  end
end

code = gets

Tester.new(code).parse
eval(code)

ルール

上記スクリプトの eval(code) に到達し、'escape!' を何らかの形で出力すればok
実は結構色々出来ます

予備知識

RipperはRubyコードをパースするライブラリで、on_identはローカル変数っぽいもので呼ばれます
で、rubyのメソッドの呼び出し方はわりとローカル変数っぽいので、ここに投げ込まれます
つまり大まかに言えば、普通のメソッドっぽいものが書かれていた時点でエラーになります

最後に

回答編は日曜夜を予定しています
ゆっくりかんがえていってね!!!

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

【Ruby】配列をループしている合間に二重配列にしていく

やろうとするたびに忘れるので,めも

sample.rb
#wordの箇所は全て同じにしましょう,hogeはお好きなように
array.map{|word|word=[word,hoge]}

ループしているうちに配列が前の方から次々と二重配列になっていく.
適宜,書き直してください.
他の処理も追加できるよ.
コードが汚いので,もっといい方法あったら教えてください.

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

【備忘録】rails newでmysql2がインストールできないエラーの解消方法

【参考】mysql2 gemインストール時のトラブルシュート

長らく悩まされていたエラーがようやく解決できたので,備忘録として。

$ rails new アプリ名 -d mysql

を実行すると,毎回このエラーが発生していた。

Fetching mysql2 0.5.2
Installing mysql2 0.5.2 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

(中略)

An error occurred while installing mysql2 (0.5.2), and Bundler cannot
continue.
Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'`
succeeds before bundling.

In Gemfile:
  mysql2
         run  bundle exec spring binstub --all
Could not find gem 'mysql2 (>= 0.4.4, < 0.6.0)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.

色々調べて試してみるも効果無し。
解決できたコマンドは【参考】に書かれていた次のコマンドだった。

$ cd アプリ名
$ gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/' -- --with-cppflags=-I/usr/local/opt/openssl/include --with-ldflags=-L/usr/local/opt/openssl/lib
$ bundle install

この後,「rails new」でアプリを作成してもエラーが出なくなった。長かった……

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

【Rials】フォロー機能1~多対多のモデルの構造

Rialsでのフォロー機能についてのメモです。
ツイッタークローンのフォロー機能のモデルの説明です。

こちらの続きです。

フォロー機能の概要

投稿機能は1対多で、
フォロー機能は、モデルの多対多の関係で成り立ちます。

投稿機能では、ユーザ(1)に対して、投稿(多)という関係が成り立ちました。
フォロー機能では、ユーザ(多)に対して、ユーザ(多)という構造を取ります。
「ユーザって言っても、自分は一人やし、1対多なんじゃないの?」と疑問に思われる方もいらっしゃるかと思いますが、半分合ってます!

前回も使わせてもらいましたが、こちらの記事を参考にしてみてください。めっちゃ分かりやすいです。
https://qiita.com/kazukimatsumoto/items/14bdff681ec5ddac26d1

多対多のモデルの構造の場合、中間にテーブルを新しく作成し、相互のやり取りをするのが一般的です。

全体像

モデル

User(ユーザ)
name:string amail:string password_digest:string

Relationship (中間テーブル)
user:references follow:references

ユーザ同士のフォロー機能なので、中間テーブルをはさむことによって、
モデルの構造はUser-Relationship-Userのようになります。
User(フォローする側)-Relationship-User(フォローされる側)と分けることが出来ます。
もう一つ細かく分けると、
User(フォローする側)-(フォローするための中間テーブル)Relationship(フォローされるための中間テーブル)-User(フォローされる側)
となります。

モデルの構成で表してみると、
User(フォローする側)-(user)Relationship(follow)-User(フォローされる側)
こうなります。

User(フォローする側)-(user)Relationship
User(フォローされる側)-(follow)Relationship
とも捉えることができます。(捉え方多すぎ!笑)

こうすると1対多+1対多となっているように見えてきませんか?
実は、多対多は、中間テーブル(多)を挟んだ1対多+1対多なのです!

モデルの作成

上記の構成を参考に、モデルの作成をしましょう。

マイグレーションファイルでの設定

中間テーブルのモデルの下記のコードのマイグレーションファイルが出来上がっているかと思います。

db/migrate/年月日時_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.0]
  def change
    create_table :relationships do |t|
      t.references :user, foreign_key: true
      t.references :follow, foreign_key: true

      t.timestamps
    end
  end
end

userもfollowもUserテーブルに紐づけたいです。
railsでは、別のテーブル名をモデルのカラム名に設定すると、自動で参照してくれます。なので、t.references :user は、Userテーブルを参照することが出来ます。

t.references :followをUserテーブルに紐づけたい。。
そんなときは、

t.references :follow, foreign_key: { to_table: :users }

こう記述すればfollowをUserテーブルに紐づけることが出来ます!
下記のコードに書き換えます。

db/migrate/年月日時_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.0]
  def change
    create_table :relationships do |t|
      t.references :user, foreign_key: true
      t.references :follow, foreign_key: { to_table: :users }

      t.timestamps

      t.index [:user_id, :follow_id], unique: true
    end
  end
end

ここで、

t.index [:user_id, :follow_id], unique: true

というコードを追記しています。unique: trueを記述することで、フォローとフォロワーがごっちゃにならないよにしています。

モデル

モデルのコードを見てみましょう。

まず、中間テーブルから。

app/models/relationship.rb
class Relationship < ApplicationRecord
  belongs_to :user
  belongs_to :follow, class_name: 'User'
end

多対多の構造は、細かく見ていくと
1対多+1対多
だと説明しました。

なので、relationshipテーブル(中間テーブル)では、haa_manyではなく、belongs_to でUserテーブルと紐づけてます。Userテーブルで、has_manyでrelationshipテーブルと紐づけることで多対他になります。

また、Userテーブルと各カラムを紐づけたいのですが、このままの状態ではuserカラムしか紐づいていないです。(railsの機能でカラムと同じ名前のテーブルを探しにいくため)
belongs_to :follow, class_name: 'User'ですが、class_name: 'User'と追加で指定することで、followカラムもUserテーブルを参照することが出来るようになります。

Userテーブルを見てみましょう!

app/models/user.rb
class User < ApplicationRecord

  has_many :microposts

  has_many :relationships
  has_many :followings, through: :relationships, source: :follow
  has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'
  has_many :followers, through: :reverses_of_relationship, source: :user
end

has_many :microposts は、投稿機能を追加したときに作成したコードです。

下の4つについて見ていきましょう。

has_many :relationshipsで、relationshipsテーブルから、データを取得してくることが出来ます。1対多の関係です。
自分がフォローしているユーザを取得します。
railsでは、同じ名前のテーブル・カラムがあると自動で取得してくれるので、RelationテーブルにUserカラムの情報を取得していることになります。

一行飛ばして、

has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'

を見てみましょう。

reverses_of_relationshipというのは自由に決められる名前です。

class_name: 'Relationship'では、Relationshipテーブルからデータを取ってきてるということを表しています。
そして、foreign_key: 'follow_id'でfollow_idのデータですよ!というようになります。

has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'

では、reverses_of_relationshipという名前の箱に、Relationshipテーブルを通して、follow_idのデータが入ると言えます。

では、

has_many :followings, through: :relationships, source: :follow


has_many :followers, through: :reverses_of_relationship, source: :user
end

について見ていきます。

throughとsourceがミソになってきます。

through: :中間テーブル, source: :カラム名
で、中間テーブルに設定されているカラムのデータを引っ張てくることが出来ます。

今回のように、ユーザがフォローしているユーザを取得したければ、中間テーブルを通過することで、

has_many :followings, through: :relationships, source: :follow
は、ユーザ(user)がフォローしているユーザ(followings)を取得

has_many :followers, through: :reverses_of_relationship, source: :user
end

で、ユーザ(User)のことをフォローしているユーザ(followers)を取得することが出来ます。

例えば、ユーザ(user)がフォローしているユーザ(followings)を取得したい場合、

    @user = User.find(params[:id])
    @followings = @user.followings.page(params[:page])

というような記述の仕方も可能になります。

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

【Rails on Rails】.envの環境変数をjsファイル内で利用する方法

はじめに

Rails内で環境変数を.envファイル内に書いておくと、ENV["環境変数名"]で利用することが出来ます。

しかし、erbファイル内では利用することが出来ますが、jsファイルで利用することが出来ません。

そこで、jsファイル内で環境変数を利用する方法を解説します。

gemのインストール

以下の2つのgemをインストールします。

Gemfileに以下の2つのgemを記入し、

Gemfile
gem 'gon'
gem 'dotenv-rails'

bundle installします。

bundle install

.envファイルの作成作成

jsファイル内で利用したい、環境変数を.envファイルに記入します。

.env
KEY = "xxx"

.gitignoreファイルを編集

念の為、GithubなどにAPI KEYなどの環境変数が公開されていように設定します。
.envに以下の1行を追加します。

.gitignore
/.env

application.html.erbの編集

application.html.erbのhead内に以下の1行を追加します。

application.html.erb
<%= include_gon %>

:triangular_flag_on_post:【注意】javascriptより上に書くこと

コントローラーの編集

環境変数を利用したいコントローラーのメソッドに追記。

xxx_controller.erb
def xxx
 gon.xxx_key = ENV['KEY']
end

:triangular_flag_on_post:KEYは.envに記述したもの

jsファイルの編集

xxx.js
const KEY = gon.xxx_key;// 環境変数

これでjsファイル内で環境変数を利用することが出来ます。

参考リンク

https://qiita.com/uma0317/items/e142661c004f68d858a5

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

読みたい・読んで良かった本

前提として、筆者はRailsメインで開発やっていてプログラマ歴3年目です。
2年ちょっと受託開発の会社に勤めており、自社サービス企業に転職しました。

役割や経験、バックグラウンドによって必要な知識は異なると思うので「ふーん」ぐらいに見ておいてください。

▼運用しやすいコードを書くために

●設計について

オブジェクト指向設計の成り立ちや有用性を知ることがまず第一歩。
そこから発展して拡張しやすい設計についてを知る必要があり、具体的な手法としてのドメイン駆動開発(DDD)についてという順序で学ぶと良さそう。

●設計原則

オブジェクト指向設計実践ガイドが参考になる。

暗記というよりは「なんかこんな原則あったな。違反してないかな?」という確認のために軽く頭に入れておいて定着するまでなんどもコードを見直して改善するみたいな運用じゃないと定着しないと思います。

- SOLID原則

オブジェクト指向設計のガイドラインの集合体。
5つの原則の頭文字をとってSOLIDと呼ばれる。

英名 和名 概要
Single Responsibility Principle 単一責任の原則 一つのクラスが持つ責任は一つでなければならない
Open/closed principle 解放閉鎖の原則 ソフトウェアのエンティティ(クラス、モジュール、関数)は拡張に対して開き、修正に対して閉じていなければならない
Liskov substitution principle リスコフの置換原則 サブクラスは、そのスーパークラスで代用可能でなければならない
Interface segregation principle インターフェース分離の原則 顧客に特化した細粒度のインタフェースを作れ。顧客は自分たちが使わないインターフェースに依存することを強いられるべきではない
Dependency inversion principle 依存性逆転の原則 依存は具象から抽象に向かって行われなければならない

- デメテルの法則

3つ目のオブジェクトにメッセージを送る際、異なる型の2つ目のオブジェクトを介することを禁じる

- DRY原則

Dot't Repeat Yourself
コードの定義は1ヶ所でのみ行い、複製ではなく参照せよ

- YAGNI原則

You Ain't Going to Need it.

必要になるまで実装するな。
将来への予想を過度に行って先回りして実装するな。

●デザインパターン

Gang of Fourと呼ばれる4人が定義した、コードの設計によく見られるパターン集。
いわゆるGoF本
デザインパターンは23パターンあり、パターンの適用ができる場合には設計をより柔軟なものにすることができる。

過度に適用しようとするのは危険だけどこれも原則同様知っておくと強力な武器になると思う。

●読みやすいコードを書く

Clean CodeやClean Architectureはこっちに被る気がしないでもない。
コード書く時間のほとんどは読んでる時間なので、拡張しやすさや開発の楽しさにおいて読みやすさは生命線。
コード規約とかベストプラクティスも併用するといいと思うのでRubocopやRails Best Practicesは積極的に活用すると良いです。

●テストを書く

- どのようにテストを導入・運用していくのか

テストを意識していないコードに対してテスト書こうとするとだいたいはテストがコードに依存する(コードが複雑なコンテキストを要求するとか)状態になったり、そもそもプロセスの見直しが必要になったりとかで、問題として単体で完結しづらい。

テストが書きやすい設計手法を知る以外にも、導入方法について包括的に知る、あるべきプロセスについて学ぶなども必要そう。

▼効率のいいコードを書くために

●SQLの効率を高める

遅くなる原因のだいたいはSQL。アンチパターンを知って避けるだけでも改善はしやすい。

SQLアンチパターンが参考になる。

●コードの効率を下げる要因を知る

Rails Best Practicesがヒントになりそう。
gemで静的解析挟んでコードレビューとか。

▼効率よくコードを書くために

▼Ruby・Railsについて学ぶ

▼安全なWebアプリケーションを作る

▼Webアプリケーションの仕組みについて知る

▼プログラマとしての原理原則や心構えを学ぶ

エッセイ形式で読みやすいものが多い。
避けるべきパターンがわかればプロセスについて組み立てたり問題を事前に察知したりしやすい。

▼効率のいい開発・運用体制を構築する

Docker、Kubernetes、CIツール、ChatOps、監視ツール(サーバー監視、アプリケーション監視、ログ監視)あたりを押さえておけばかなり効率化が図れるようになるんじゃないかと思います。

▼開発手法・開発体制について学ぶ

個人の能力をさらに活かすためにはプロセスやチームのあり方が重要になる。
カイゼンジャーニーが一番実践的。

▼組織・プロセスについて学ぶ

開発チームの改善とかを考え始めると結局組織のあり方・プロセスのあり方そのものに行き着くなと思うので。

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

[Rails]C3.js × gonでチャートを表示する

できるだけ簡単に、かつクオリティの高いチャートを表示したい・・・。
そんなあなたにはC3.jsがオススメです。
また、データベースやコントローラーにあるデータを表示したい場合はgem gonを使うと便利です。

今回は、C3.jsとgonを使った以下のような棒グラフの描画方法を紹介します。
スクリーンショット 2019-07-19 14.34.46.png

環境

バージョン

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin18]
$ rails -v
Rails 5.2.3

導入gem

gonを導入します。

gem 'gon'
gem 'haml-rails'

ビューにはhamlを採用しています。

ライブラリとgonの読み込み

C3.jsはD3.jsのラッパーライブラリーなのでC3.jsと一緒にD3.jsを読み込みます。
今回はCDNを使って読み込むことにしました。(もちろん、gemとして読み込むこともできます。RailsでC3.jsを使う
また、gonを適用するためにheadタグの中で以下のように追記します。

application.html.haml
    # jsファイルよりも前に読み込む
    = Gon::Base.render_data
    %link{ href: "https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.7/c3.css", rel: "stylesheet"}
    %script{src: "https://d3js.org/d3.v4.min.js", type: "text/javascript"}
    %script{src: "https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.7/c3.js", type: "text/javascript"}

ビュー

チャートを挿入したい要素にIDを指定しておきます。

html.haml
.container
  #bar
  / ここにチャートを挿入

コントローラー

gon.◯◯という変数に値を代入することでjsファイルで呼び出すことができます。

controller.rb
    gon.x = ["x","07/13","07/14","07/15","07/16","07/17","07/18","07/19"]
    gon.cash = ["現金",2000000,1000000,1000000,1000000,1000000,1000000,1000000]
    gon.crypto = ["仮想通貨",8000000,9000000,10000000,8000000,6000000,4000000,2000000]

わかりやすくするため値を直接代入していますが、
コントローラーでDBの情報を取得してjsに変数を渡したいときに利用すると便利です。

jsファイル

同様にgon.◯◯とすることでコントローラで定義した変数を取り出すことができます。

window.onload = function() {
// 棒グラフを挿入
  var barGraph = c3.generate({
    bindto: '#bar', // 挿入する要素のIDを指定(デフォルトは#chart)
    data: {
      x : 'x',
      columns: [
        // コントローラーで定義した変数の呼び出し
        gon.x, //x軸のメモリに表示する文字列
        gon.cash, // 値1
        gon.crypto, // 値2
      ],
      groups: [
          // 値1と値2を連結させた棒グラフを表示
          ['現金', '仮想通貨']
      ],
      type: 'bar' // チャートタイプを指定(今回は棒グラフ)
    },
    axis: {
      x: {
        type: 'category' // x軸のメモリ(今回は日付)を読み込むために必要
      },
      y: {
        label: {
          // y軸のラベルを追加
          text: '価格 / 円',
          position: 'outer-middle'
        },
        tick: {
          // 3桁ずつ「,」を挿入
          format: d3.format(",")
        }
      }
    },
    // チャートの余白を指定できる
    padding: {
      top: 10,
      bottom: 0
    }
  });
}

指定できるチャートタイプやその他設定は公式リファレンスがわかりやすいです。C3.js

gonの仕組み

application.html.hamlのheadタグの中でGon::Base.render_dataを記述した場所がどう表示されているかを確認してみます。

html
<script>
  //<![CDATA[
  window.gon={};
  gon.x=["x","07/13","07/14","07/15","07/16","07/17","07/18","07/19"];
  gon.cash=["現金",2000000,1000000,1000000,1000000,1000000,1000000,1000000];
  gon.crypto=["仮想通貨",8000000,9000000,10000000,8000000,6000000,4000000,2000000];
  //]]>
</script>

window.gon={}とすることでgonというグローバル変数を定義し、空のオブジェクトを代入して初期化しています。(グローバル変数はwindowオブジェクトのプロパティです)
そして、コントローラーで指定した値をgonのプロパティとして代入しています(windowは省略可能)。
これにより、jsファイルで変数を読み込むことが可能になります。

参考

C3.js
gon
RailsでC3.jsを使う

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

Selenium::WebDriver::Element#click が position: fixed のヘッダやフッタに邪魔されて失敗するときに回避する方法

事象

ヘッダとフッタが position: fixed; で固定されたUIを持ったWebページのe2eテストとか書いてるとクリックできないよーってエラーがよく起きる。こんな感じのUIで:

See the Pen fixed header and footer by oieioi (@oieioi) on CodePen.

このページに対してこんなスクリプトを実行すると:

test.rb
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
driver.navigate.to "http://127.0.0.1:8081/"
element = driver.find_element(name: 'button')
element.click
driver.quit

こんなエラーになる。 fixed された <footer> とクリックしたい <button> が被っているためクリックできないという。

Element <button name="button">...</button> is not clickable at point (40, 881). Other element would receive the click: <footer>...</footer>

解決策

目標をセンターに入れてクリックする。

色々方法はあるが、今回はJavaScriptのElement.scrollIntoView() を使って解決した。以下を click する前に実行すれば目標の要素を真ん中になるようにスクロールするので、クリックできるようになる。

driver.execute_script("arguments[0].scrollIntoView({behavior: 'auto', block: 'center', inline: 'nearest'});", element)

で、この動作を click をモンキーパッチして行う Gem をつくりました。

https://github.com/oieioi/selenium-webdriver-element-extend_click_again

test.rb
require 'selenium-webdriver'
require 'selenium/webdriver/element/extend_click_again'
...

たまに便利です。

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

Selenium::WebDriver::Element#click が position: fixed のヘッダやフッタに邪魔されて not clickable になるとき回避する方法

事象

ヘッダとフッタが position: fixed; で固定されたUIを持ったWebページのe2eテストとか書いてるとクリックできないよーってエラーがよく起きる。こんな感じのUIで;

See the Pen fixed header and footer by oieioi (@oieioi) on CodePen.

このページに対してこんなスクリプトを実行すると;

test.rb
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
driver.navigate.to "http://127.0.0.1:8081/"
element = driver.find_element(name: 'button')
element.click
driver.quit

こんなエラーになる。 fixed された <footer> とクリックしたい <button> が被っているためクリックできないという。

Element <button name="button">...</button> is not clickable at point (40, 881).
Other element would receive the click: <footer>...</footer>

解決策

目標をセンターに入れてクリックする。

色々方法はあるが、今回はJavaScriptの Element.scrollIntoView() を使って解決した。以下を click する前に実行すれば目標の要素を真ん中になるようにスクロールするので、クリックできるようになる。

driver.execute_script("arguments[0].scrollIntoView({behavior: 'auto', block: 'center', inline: 'nearest'});", element)

で、この動作を click をモンキーパッチする Gem をつくりました。

https://github.com/oieioi/selenium-webdriver-element-extend_click_again

使い方は require するだけです。

test.rb
require 'selenium-webdriver'
require 'selenium/webdriver/element/extend_click_again'
...

たまに便利です。

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

Selenium::WebDriver::Element#click がfixedのヘッダやフッタに邪魔されて失敗するとき回避するパッチを作った

事象

ヘッダとフッタが position: fixed; で固定されたUIを持ったWebページのe2eテストとか書いてるとクリックできないよーってエラーがよく起きる。こんな感じのUIで:

See the Pen fixed header and footer by oieioi (@oieioi) on CodePen.

このページに対してこんなスクリプトを実行すると:

test.rb
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
driver.navigate.to "http://127.0.0.1:8081/"
element = driver.find_element(name: 'button')
element.click
driver.quit

こんなエラーになる。 fixed された <footer> とクリックしたい <button> が被っているためクリックできないという。

Element <button name="button">...</button> is not clickable at point (40, 881). Other element would receive the click: <footer>...</footer>

解決策

目標をセンターに入れてクリックする。

色々方法はあるが、今回はJavaScriptのElement.scrollIntoView() を使って解決した。以下を click する前に実行すれば目標の要素を真ん中になるようにスクロールするので、クリックできるようになる。

driver.execute_script("arguments[0].scrollIntoView({behavior: 'auto', block: 'center', inline: 'nearest'});", element)

で、この動作を click をモンキーパッチして行う Gem をつくりました。

https://github.com/oieioi/selenium-webdriver-element-extend_click_again

test.rb
require 'selenium-webdriver'
require 'selenium/webdriver/element/extend_click_again'
...

たまに便利です。

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

Rails6 のちょい足しな新機能を試す56(create_table if_not_exists編)

はじめに

Rails 6 に追加されそうな新機能を試す第56段。 今回は、 create_table if_not_exists 編です。
Rails 6 では、 create_table:if_not_exists オプションが追加されました。

Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は gem install rails --prerelease でインストールできます。

$ rails --version
Rails 6.0.0.rc1

マイグレーションファイルを作る

$ bin/rails g migration CreateUser name

マイグレーションファイルを修正する

:if_not_exists オプションを指定します。

db/migrate/20190713221959_create_user.rb
class CreateUser < ActiveRecord::Migration[6.0]
  def change
    create_table :users, if_not_exists: true do |t|
      t.string :name
    end
  end
end

データベースを作成する

データベースを作成します。

$ bin/rails db:create

psql でテーブルを作ります

先に users テーブルを psql で作成します。
ちょっと手抜きで id カラムは省略します。

app_development=# create table users ( name varchar(256) )
CREATE TABLE

テーブルができたことを確認しておきます。カラムの情報も確認します。

app_development=# \dt
                List of relations
 Schema |         Name         | Type  |  Owner
--------+----------------------+-------+----------
 public | ar_internal_metadata | table | postgres
 public | schema_migrations    | table | postgres
 public | users                | table | postgres
(3 rows)

app_development=# \d users;
                       Table "public.users"
 Column |          Type          | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
 name   | character varying(256) |           |          |

マイグレーションを実行する

マイグレーションを実行します。エラーは発生しません。

$ bin/rails db:migrate
== 20190713221959 CreateUser: migrating =======================================
-- create_table(:users, {:if_not_exists=>true})
   -> 0.0019s
== 20190713221959 CreateUser: migrated (0.0019s) ==============================

psql で users テーブルを確認すると id カラムが無いので、マイグレーションによってテーブルはできなかったことがわかります。

app_development=# \d users;
                       Table "public.users"
 Column |          Type          | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
 name   | character varying(256) |           |          |

ロールバックする

ロールバックしてみます。

$ bin/rails db:rollback
== 20190713221959 CreateUser: reverting =======================================
-- drop_table(:users, {:if_not_exists=>true})
   -> 0.0031s
== 20190713221959 CreateUser: reverted (0.0043s) ==============================

psql で確認すると users テーブルが削除されていることがわかります。

app_development=# \dt
                List of relations
 Schema |         Name         | Type  |  Owner
--------+----------------------+-------+----------
 public | ar_internal_metadata | table | postgres
 public | schema_migrations    | table | postgres
(2 rows)

マイグレーションを実行する

users テーブルが無い状態でマイグレーションしてみます。

$ bin/rails db:migrate

psql で確認するとテーブルが作られていることがわかります。

app_development=# \dt
                List of relations
 Schema |         Name         | Type  |  Owner
--------+----------------------+-------+----------
 public | ar_internal_metadata | table | postgres
 public | schema_migrations    | table | postgres
 public | users                | table | postgres
(3 rows)

カラムの情報を確認すると id カラムも存在しており、 migration によって作られたことがわかります。

app_development=# \d users;
                                 Table "public.users"
 Column |       Type        | Collation | Nullable |              Default
--------+-------------------+-----------+----------+-----------------------------------
 id     | bigint            |           | not null | nextval('users_id_seq'::regclass)
 name   | character varying |           |          |
Indexes:
    "users_pkey" PRIMARY KEY, btree (id)

テーブルを削除してからロールバックする

psql で users テーブルを削除します。

app_development=# drop table users

ロールバックするとエラーが発生します。

$ bin/rails db:rollback
== 20190713221959 CreateUser: reverting =======================================
-- drop_table(:users, {:if_not_exists=>true})
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:

PG::UndefinedTable: ERROR:  table "users" does not exist

エラーを回避するには

エラーを回避するには、

db/migrate/20190713221959_create_user.rb
class CreateUser < ActiveRecord::Migration[6.0]
  def change
    create_table :users, if_not_exists: true, if_exists: true do |t|
      t.string :name
    end
  end
end

とするか

db/migrate/20190713221959_create_user.rb
class CreateUser < ActiveRecord::Migration[6.0]
  def up
    create_table :users, if_not_exists: true do |t|
      t.string :name
    end
  end

  def down
    drop_table :users, if_exists: true
  end
end

とすれば良いようです。後者の方が直感的でわかりやすいので、個人的にはオススメです。
(というか前者は訳わからんし、将来、 ActiveRecord の動作が変わったりすると痛い目に合いそうです。)

試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try056_create_table_if_not_exists

参考情報

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

An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling.と出てきたときの対処法

最近の勉強で学んだ事を、ノート代わりにまとめていきます。
主に自分の学習の流れを振り返りで残す形なので色々、省いてます。
Webエンジニアの諸先輩方からアドバイスやご指摘を頂けたらありがたいです!

どういうエラーなのか

railsのリポジトリをクローンしてきてちゃんと動くか確かめようとした時に発生しました!
やったことはbundle installをしたら最後に以下のような感じのものが出てきました

//省略
An error occurred while installing mysql2 (0.5.2), and Bundler
cannot continue.
Make sure that `gem install mysql2 -v '0.5.2' --source
'https://rubygems.org/'` succeeds before bundling.

エラーに対応策が書いていたので

gem install mysql2 -v '0.5.2' --source'https://rubygems.org/'

そのまま入力したのですが解決されず

なんなんやこれはって思っていたら以下の記事を見つけました。恐らくほとんど一緒のエラー?
なのかと思うのですが記事に書いてあるとの解決策を試したところ問題解決しました。
mysql2 gemインストール時のトラブルシュート

$ gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/' -- --with-cppflags=-I/usr/local/opt/openssl/include --with-ldflags=-L/usr/local/opt/openssl/lib
Building native extensions with: '--with-cppflags=-I/usr/local/opt/openssl/include --with-ldflags=-L/usr/local/opt/openssl/lib'

このように入力したらやっと問題なくbundle installできました。
Railsではこのようなエラーに遭遇することが多いのですがなぜ、そもそもこのようなエラーが起きているのかの原因究明を参考にした記事のようにできていなかったので全てできるかは分からないですが徐々にしていきたいと思います。

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

nokogiri が macOS でインストールできない

環境

  • macOS High Sierra 10.13.6
  • Ruby 2.3.8 (わけあって古い)

現象

macOS で nokogiri を bundle install すると、事前に brew install libxml2 しているにもかかわらず libxml2 が見つからないと言われて失敗します。

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

   current directory: 省略
checking if the C compiler accepts ... yes
checking if the C compiler accepts -Wno-error=unused-command-line-argument-hard-error-in-future... no
Building nokogiri using system libraries.
Using pkg-config gem version 1.1.9
checking for libxml-2.0... no
checking for libxslt... no
checking for libexslt... no
ERROR: cannot discover where libxml2 is located on your system. please make sure `pkg-config` is installed.
*** extconf.rb failed ***

対処

既知の対処法として、下記を事前に実行していましたが、うまくいきませんでした。いろいろググってみましたがわからず。

bundle config build.nokogiri --use-system-libraries

nokogiri 公式 を確認すると、以下を指定するようにとあり、そのとおりにしたらうまくいきました。

bundle config build.nokogiri --use-system-libraries \
  --with-xml2-include=$(brew --prefix libxml2)/include/libxml2

結論

公式は見よう。

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

Pay.jpを使用してクレジットカード登録削除機能の実装をしてみた

はじめに

軽く自己紹介から
TECH::EXPRETというプログラミングスクールに通っております。
53期で活動して、そろそろ終わりに近づいてきましたので、実装した部分くらいは
アウトプットするかーってことで、やります。
テストはやってないので、それ目的の方はごめんなさい。

今回この記事を書く目的は3つほどあります。

1.チームメンバーに学んだ技術の共有の為

なんか作ったらわかりやすいかなーと思ってます

2.就活で学んだことをしっかりアウトプットできますよとアピールする為

話すよりも論理的に組み立てているか、全体像をみて考えているかをこういうところでみてもらう為

3.アウトプットを通して、今回学んだ内容をしっかり定着させる為

ぶっちゃけ忘れているところが多々あります笑 思い出すためにもしっかり書きます笑

開発環境

ruby 2.5.1
rails 5.2.1
mysql

ゴール

以下のように登録をrails5でできるようにしましょう

  • 実際のビュー Image from Gyazo
  • Sequel Pro の CreditCardの画面 スクリーンショット 2019-07-07 15 47 21
  • payjpの画面 スクリーンショット 2019-07-07 15 44 37

前提準備

deviseでのユーザログイン機能の実装

処理の概要

1) pay.jpのgemをインストール

作業目的:Pay.jpを使用できるようにする為

Gemfile
gem 'payjp'

2) Pay.jpの公式のjavascriptを読み込ませられるようする

作業目的:Pay.jpの公式のjavascriptを読み込ませられるようする為

app/views/layouts/application.html.haml
%script{src: "https://js.pay.jp/", type: "text/javascript"}

3) クレジットカードテーブルを作成する

作業目的: クレジットカード情報を保存するテーブルを作成

Colun Type Options 意味
user_id references null: false, foreign_key: true ログインユーザ
costomer_id integer null: false 顧客ID情報(pay.jpから返ってくるデータ)
card_id integer null: false カードID情報(pay.jpから返ってくるデータ)

costomer_id, card_idは、カード情報(16桁のやつ),有効期限年月,セキュリティーコードを渡すとpay.jpから返ってくるデータのこと

db/migrate/***************_create_credit_cards.rb
class CreateCreditCards < ActiveRecord::Migration[5.2]
  def change
    create_table :credit_cards do |t|
      t.references :user ,foreign_key: true, null: false
      t.string :customer_id, null: false
      t.string :card_id, null: false
      t.timestamps
    end
  end
end

なんで、DBにカード情報とか保存しないの?っと疑問に持たれる人もいると思いますので、ここで解説
以下のURLを参照していただければわかるのですが、
Pay.jp側の方で、
セキュリティーの観点から
クレジット情報は、勝手にDBに保存するな。俺が管理する。お前らにはIDやるから我慢しろ。っと言われているからなんですよねー
めんどいですが、クレジット情報や購入情報は毎回Pay.jpに問い合わせる必要があります。

http://payjp-announce.hatenablog.com/entry/2017/11/10/182738

4) クレジットカード情報の追加と削除のコントローラを作っていきます

1つのget、3つのpostの実装

get

  • edit

post

  • create
    payjpとCardのデータベース作成を実施します。
  • delete
    payjpとCardのデータベースを削除する。
  • show
    DBのCreditCard情報を、payjpに送りcustomer情報を取り出すために実装

感想

まだ途中です。。。。。

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

【Programming News】Qiitaまとめ記事 July 18, 2019 Vol.4

筆者が昨日2019/7/18(木)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら

Java

Python

Node.js

Nuxt.js

Swift

Laravel

MySQL

Git

Visual Studio

AWS

Docker

Google Apps Script

Develop

Raspberry

UML

Go言語

R言語

awk

LaTex

Redmine

更新情報

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

Railsでメタプログラミング(黒魔術)と呼ばれるsendメソッドを活用してみた

はじめに

この記事を書こうと思ったのはsendメソッドを活用するイメージが湧かない方に、僕はRailsでこんな感じで使う場面がありましたよー的な意味で紹介するために書きました。
sendメソッドを使うのが最善かどうかは置いといて...笑

メタプログラミングとは?

まずメタプログラミングの意味を軽く紹介します。
Railsチュートリアルではこのように説明されてます。

メタプログラミングを一言で言うと「プログラムでプログラムを作成する」ことです。メタプログラミングはRubyが有するきわめて強力な機能であり、Railsの一見魔法のような機能 (「黒魔術」とも呼ばれます) の多くは、Rubyのメタプログラミングによって実現されています。

プログラムでプログラムを作成する?ちょっとこの説明だけ読んでもよくわからないですね笑
Railsチュートリアルでは実際にsendメソッドというものを利用してメタプログラムを作成していますのでそちらを見るとよりイメージが湧くかも。
※この後実際に例を用いて説明します

sendメソッドとは?

Rubyにはsendというメソッドがあります。
どのように使うか簡単な例で説明します。

例えば、upcaseという文字列を大文字に変換して出力するメソッドを例に使います。

string = "ruby"  #=> "ruby"
string.upcase  #=> "RUBY"
string.send(:upcase)  #=> "RUBY"
string.send("upcase")  #=> "RUBY"

このようにsendメソッドの引数に呼び出したいメソッド名のシンボルか文字列を渡すと通常通りメソッドの呼び出しが起こります。

では次に引数を持つメソッドを呼び出したい時にどのようにするか、splitメソッド例を出しましょう。
Railsチュートリアルではここまでは説明されていませんが意外と簡単です。

splitメソッドは文字列などを引数に渡した区切りで分割して配列にしてくれます。

string = "aaaaaxbbbbbxccccc"  #=> "aaaaaxbbbbbxccccc"
string.split("x")  #=> ["aaaaa", "bbbbb", "ccccc"]
string.send(:split, "x")  #=> ["aaaaa", "bbbbb", "ccccc"]
string.send("split", "x")  #=> ["aaaaa", "bbbbb", "ccccc"]

引数をメソッドに渡したい場合sendメソッドの第二引数以降に渡します。

Railsでsendメソッド活用

では本題に入ります。
RailsでECサイトを作っていたのですが、3つのモデル(Artist, Label, Genre)のデータを一覧表示するviewがほぼ同じだったのでこれをパーシャル化してしまおうという場面で使いました。
どのようなレイアウトか一応簡単なモックアップを載せておきます。(かなり雑なのは気にしないでください笑)

モックアップ

pacto_orbis(admin)-アーティスト一覧.png
pacto_orbis(admin)-ジャンル一覧.png
pacto_orbis(admin)-レーベル一覧.png

うん、これはもう表示するデータしかほぼ変わらないですね。これをパーシャル化せずに何をする?ということでパーシャル化しました。

コントローラ

app/controllers/artists_controller.rb
# 中略
  def index
    @artists = Artist.page(params[:page])
    @artist = Artist.new
  end
# 中略
app/controllers/genres_controller.rb
# 中略
  def index
    @genres = Genre.page(params[:page])
    @genre = Genre.new
  end
# 中略
app/controllers/labels_controller.rb
# 中略
  def index
    @labels = Label.page(params[:page])
    @label = Label.new
  end
# 中略

ビュー

app/views/artists/index.html.erb
<h2>アーティスト一覧</h2>
<%= render 'layouts/object_list', objects: @artists, object: @artist %>
app/views/genres/index.html.erb
<h2>ジャンル一覧</h2>
<%= render 'layouts/object_list', objects: @genres, object: @genre %>
app/views/labels/index.html.erb
<h2>レーベル一覧</h2>
<%= render 'layouts/object_list', objects: @labels, object: @label %>
app/views/layouts/_object_list.html.erb
<div class="row">
  <div class="col-sm-5">
    <%= render 'layouts/error', object: object %>
    <%= form_with model: object, local: true do |f|  %>
      <%= f.label :name %>
      <%= f.text_field :name %>
      <%= f.submit "登録" %>
    <% end %>
  </div>
  <div class="col-sm-7">
    <div class="index-wrapper">
      <% object_name = object.class.to_s.downcase  # それぞれのオブジェクトのクラス名を変数に代入 %>
      <% objects.each do |data| %>
        <% edit_path = self.send("edit_#{object_name}_path", data)  ### ここで使用!!### %>
        <% destroy_path = self.send("#{object_name}_path", data)  ### ここで使用!!### %>
        <%= data.name %>
        <%= link_to "編集", edit_path %>
        <%= link_to "削除", destroy_path, method: :delete, data: { "confirm" => "本当に削除しますか?" } %>
      <% end %>
    </div>
    <%= paginate objects, class: "pagination" %>
  </div>
</div>

それぞれのindex.html.erbで書かれているパーシャルテンプレートの呼び出しは大丈夫だと思います。(わからない方はググってください)
_object_list.html.erbのコードで今回テーマのsendメソッドに関係するコードだけ説明していきます。

解説

まずそれぞれ渡されたオブジェクトのクラス名(全部小文字)を変数に代入します。
何故かは後々わかります。

<% object_name = object.class.to_s.downcase  # それぞれのオブジェクトのクラス名を変数に代入 %>

一つずつ説明すると、
.class → 呼び出したオブジェクトのクラスを取得
.to_s → 文字列に変換
.downcase → 文字列を小文字に変換

なのでまとめると

# 渡ってきた元のインスタンス変数 → object_nameに入る文字列
@artist  "artist"
@genre  "genre"
@label  "label"

となります。

次に本題のsendメソッドのコード

<% edit_path = self.send("edit_#{object_name}_path", data)  ### ここで使用!!### %>
<% destroy_path = self.send("#{object_name}_path", data)  ### ここで使用!!### %>

ここでしたいことは、edit_path, destroy_pathそれぞれにeachメソッドで取り出されるオブジェクト+パーシャルの呼び出しで渡ってきたオブジェクトの種類(artistかgenreかlabel)によって動的にパスを生成し、代入するということです。
つまり、呼び出すメソッドを動的に決めるということ。これがRubyのメタプログラムです。

sendメソッドの実行結果は以下のようになります。

# artist
edit_path = edit_artist_path(data)
destroy_path = artist_path(data)
# genre
edit_path = edit_genre_path(data)
destroy_path = genre_path(data)
# label
edit_path = edit_label_path(data)
destroy_path = label_path(data)

ここで、ん?self.ってなに?てか、railsの~_pathってメソッドなの?メソッドって***.メソッドって感じで何かオブジェクトに対して呼び出すものじゃないの?
って思うかもしれません。特に他の言語(JavaScriptやPHPなど)をやったことがありrubyやrailsを始めたばかりの方なら~_pathは関数じゃないの?って思うかもしれませんが、メソッドです!!
というかRubyには関数がありません。

この話をすると長くなってしまうので少し省略しますが、Rubyは全てがオブジェクトであり~_pathもなんらかのオブジェクトのメソッドになるのです。そして自身のメソッド呼び出す際には
self.メソッドという感じで書きます。しかしこれはself.を省略できてメソッド名だけで呼び出すことが可能です。

つまり、~_pathは普段はself.が省略されていて、実際にはself.~_pathでも呼び出せます。

じゃあsendメソッドを呼び出すときもself.を省略できるのかと。
その通りです。上で紹介したコードはさらに短くこのようにできます。

<% edit_path = send("edit_#{object_name}_path", data)  ### ここで使用!!### %>
<% destroy_path = send("#{object_name}_path", data)  ### ここで使用!!### %>

今回説明しやすいようにはじめはself.をつけた状態にしておきましたが、これが短く書くとしたら完成系になります。

まとめ

実際sendメソッドなんて使わなくてももっと簡単にできます。
prefix(~_path)を使わずに直接相対パスを書き、その相対パスに変数を入れ込めばそこまで難しいことではないでしょう。
しかし、ただただこの黒魔術と呼ばれるメソッドを使いたかったのです。笑
皆さんも遊び感覚であえて難しい実装方法を選んでみても面白いかもしれませんよ〜。

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