- 投稿日:2020-01-26T22:54:11+09:00
【初心者向け】Rubyの引数についてまとめてみました。
Rubyで使われている引数についてまとめてみました。
参考記事
https://qiita.com/rtoya/items/33617078501776fdcad7
http://techracho.bpsinc.jp/baba/2013_12_26/15026初心者の方がおそらく最初に習う引数
#hogeという命令はmsgを出力する def morning(msg) puts msg end #msgはおはーという文章を出力する morning = "おはよー" #hogeを実行する morning(morning)仮引数と実引数(本引数)は一緒じゃなくてよかったですもんね。
morning(morning)のカッコでくくってるところのmorningが実引数で
msgが仮引数私は頭の中でこう考えてます。間違ってましたらご指摘いただけると幸いです・・・・
#仮引数領域がここから〜〜〜 def morning(msg) puts msg end #〜〜〜ここまで #実引数領域がここから〜〜〜 morning = "おはよー" morning(morning) #〜〜〜ここまで何をいいたいのか?というと、
def morning(msg)で宣言しているのに
puts morning
end
っとしてしまうとエラーが起きてしまいますよ〜っということです。間違っている例
def morning(msg) puts morning end morning = "おはよー" morning(morning)エラーが出ます。
↓wrong number of arguments (given 0, expected 1) (ArgumentError)実引数で使用するものがmsgならmsgを中身でも使わないといけないということですね。
最初は自分も割と混乱してました・・・・。デフォルト引数
def hello(hello = "こんにちは") puts hello end hellodef外に宣言せずに、直接仮引数側で引数を設定するやり方みたいですね。
デフォルト値をつけないキーワード引数
def hello(hello: ) puts hello end hello(hello: "こんにちは")こんなふうに、デフォルトで引数を定めなくても利用できるみたいですね。
ハッシュを使って引数指定
def evening(evening: "こんばんは") puts evening end eveningハッシュで使用できるので、データの取り出しに便利ですね。
ハッシュを使って引数指定②
def morning_hello(morning:"おはー", hello:"こんにちは") puts "#{morning}、そして#{hello}" end morning_helloこんなふうに、ハッシュなら順番をきにせずデータを自由に取り出せるみたいです。
可変引数
def morning_hello_evening(*msg) puts msg end morning_hello_evening('morning','hello','evening')こちらの記事を参照いたしました。わかりやすいです。
https://shinkufencer.hateblo.jp/entry/2018/09/15/230150どうやら配列でデータを格納しているみたいですね。
可変長引数 2(配列で格納されているものをそれぞれ出力してみる)
本当に配列で格納されているのか?疑い深い私は実際に試しました。
def morning_hello_evening(*msg) puts "配列に格納されている#{msg[0]}" puts "配列に格納されている#{msg[1]}" puts "配列に格納されている#{msg[2]}" end morning_hello_evening('morning','hello','evening')↓
解説morning_hello_evening('morning','hello','evening')が
# msg = ['morning','hello','evening'] っとなっており、0がmorning、1がhello、2がeveningとなっているオプション引数
def morning_hello_evening(**msg) puts msg end morning_hello_evening(morning: 'morning',hello: 'hello',evening: 'evening')こんな感じで
ちょっと、可変長引数と、オプション引数がわかりずらかったので調べました・・・・可変長引数が配列
なのに対して
オプション引数はハッシュ
で複数の引数を受け取れる
っという違いであってますでしょうか?
間違ってたらすいません。。ブロック引数
def morning(&block) block.call end morning{ p 'おはー' }知識不足でブロック引数を使うメリットがよくわかりませんでした・・・
追々勉強しなおしていこうかと・・・ブロック引数(yield使用ver)
def morning(&block) yield end morning{ p 'おはー' }こちらも同上です、追々使用メリットがわかるよう日々学習していきます。
以上です。
もし、これは違う、これはこうしたほうが良い等々ございましたらご指摘いただけますと幸いです。最後までみていただき、ありがとうございます。
- 投稿日:2020-01-26T22:35:05+09:00
Rails Minitest こんな時どうする?テストクラス毎にfixureを分けたい
概要
テストクラス毎にfixureを分けたい。
詳細
Minitestではテストデータをfixtureフォルダにyaml形式のファイルで配置しておかなければならない。yamlファイル名はモデル名であり、アプリケーション全体で1モデル=1fixutureファイルが基本である。
しかし、アプリケーションが複雑になってくると1モデル=1ファイルですべてのテストケースを網羅するようなテストデータを用意するのが難しくなることがある。そこでテストクラス毎にfixureファイルを分けることでテストケースを網羅するテストデータを用意し易くする。
対応
アプリケーション全体で1モデル=1ファイルを一度に読み込むようになっているのを止める。
それぞれのテストクラスで任意のfixtureを読み込むようにする。サンプルコード
サンプルでは2つのテストクラス book_test.rb と user_test.rb があり、それぞれのテストクラスでBookモデルのテストデータを別々のfixtureから読み込む。
アプリケーション全体で1モデル=1ファイルを一度に読み込むようになっているのを止める。
通常はtest/fixuresフォルダ以下のfixureファイルを読み込む。test/test_helper.rENV['RAILS_ENV'] ||= 'test' require_relative '../config/environment' require 'rails/test_help' class ActiveSupport::TestCase # ここでfixtureを一度に読み込むようになっているのでコメントアウトする # fixtures :all endこのテストクラスでは test/fixtures/book_test フォルダにある books.yaml をBookモデルに読み込むようにする。
test/book_test.rbrequire 'test_helper' class BookTest < ActiveSupport::TestCase fixture_directory = Rails.root.join("test", "fixtures", "book_test") fixture_files = [:books] fixture_models = {:book => Book} ActiveRecord::FixtureSet.create_fixtures(fixture_directory, fixture_files, fixture_models, ActiveRecord::Base) test "テスト" do # テストコード end endこちらのテストクラスでは test/fixtures/user_test フォルダにある books.yaml をBookモデルに読み込むようにする。このサンプルでは別のモデルであるUserのfixureも読み込んでいる。
test/user_test.rbrequire 'test_helper' class UserTest < ActiveSupport::TestCase fixture_directory = Rails.root.join("test", "fixtures", "user_test") fixture_files = [:books, :users] fixture_models = {:book => Book, :user => User} ActiveRecord::FixtureSet.create_fixtures(fixture_directory, fixture_files, fixture_models, ActiveRecord::Base) test "テスト" do # テストコード end end
- 投稿日:2020-01-26T20:29:32+09:00
【徒然なるままに】2020年最新、WEB勢力図を三国志にしてみた
今は昔
一昔前はTwitterのScala/FacebookのReactが猛威を振るっていた時期がありましたが、
イキリ勢力の消耗戦で、最近はVue勢がかなり盛り上がっている印象を受けます。かつてのJQuery、それがVue
そんな予感がひしひしとします。
そこで、最新のWEB勢力図を三国志風にしてみました。
三国志の知識はマンガとゲームとWikiです、すみません。全く知らないキッズは中田さんのYoutube大学をご覧ください。
【三国志】第一話〜英雄たちの夜明け〜ついに授業リクエストNo.1の超大作魏
- 曹操(PHP) … 何でもあり最強、女を侍らせる。
- 夏侯惇(Laravel) … ロマンチスト
- 司馬懿(Vue) … 超現実主義、パクリもありあり
呉
- 孫権(Ruby) … なんとなく
- 陸遜(Rails) … 堅実な感じ
- 周瑜(Angular) … 超優秀だがぱっとしない
蜀
- 劉備(Scala) … 形式(礼)に拘る、前漢(Java)の末裔
- 関羽(Playframework) … RESTを世に広めた貢献者
- 孔明(React) … 革新者、新時代を拓く智彗者
後漢、晋は図の通りです
面白そうなので、図はクリエイティブ・コモンズ(表示—非営利—継承)にしときます。
勝手に改編(罵倒)してCCでお願いします。Reactはreact-routerとhooksでものすごい簡単になったので、
是非、新時代を体験してください。新型Reactとは
簡単に書くと、これだけです。
このアトムを融合していくだけです。const 関数 = () => { ~ステート式ES6スクリプト~ render ( <div>~</div> ) } export default 関数
- 投稿日:2020-01-26T20:29:32+09:00
【徒然なるままに】2020年最新、WEB技術の勢力図を三国志にしてみた
今は昔
一昔前はTwitterのScala/FacebookのReactが猛威を振るっていた時期がありましたが、
イキリ勢力の消耗戦で、最近はVue勢がかなり盛り上がっている印象を受けます。かつてのJQuery、それがVue
そんな予感がひしひしとします。
そこで、最新のWEB勢力図を三国志風にしてみました。
三国志の知識はマンガとゲームとWikiです、すみません。全く知らないキッズは中田さんのYoutube大学をご覧ください。
【三国志】第一話〜英雄たちの夜明け〜ついに授業リクエストNo.1の超大作魏
- 曹操(PHP) … 何でもあり最強、女を侍らせる。
- 夏侯惇(Laravel) … ロマンチスト
- 司馬懿(Vue) … 超現実主義、パクリもありあり
呉
- 孫権(Ruby) … なんとなく
- 陸遜(Rails) … 堅実な感じ
- 周瑜(Angular) … 超優秀だがぱっとしない
蜀
- 劉備(Scala) … 形式(礼)に拘る、前漢(Java)の末裔
- 関羽(Playframework) … RESTを世に広めた貢献者
- 孔明(React) … 革新者、新時代を拓く智彗者
後漢、晋は図の通りです
面白そうなので、図はクリエイティブ・コモンズ(表示—非営利—継承)にしときます。
勝手に改編(罵倒)してCCでお願いします。Reactはreact-routerとhooksでものすごい簡単になったので、
是非、新時代を体験してください。新型Reactとは
簡単に書くと、これだけです。
このアトムを融合していくだけです。const 関数 = () => { ~ステート式ES6スクリプト~ render ( <div>~</div> ) } export default 関数
- 投稿日:2020-01-26T20:02:05+09:00
Get News Titles from Google News
Google ニュースの RSS からタイトルを取得します。
google_news_title.rbrequire 'rss' # 検索ジャンル (以下のいづれか) # WORLD, NATION, BUSINESS, TECHNOLOGY, ENTERTAINMENT, SPORTS, # SCIENCE, HEALTH SEARCH = 'TECHNOLOGY' def encode_url(url) URI.encode url.encode('EUCJP') end url = "https://news.google.com/rss/search?q=#{SEARCH}&hl=ja&gl=JP&ceid=JP:ja" RSS::Parser.parse(encode_url(url), true, false).channel.items.each do |item| puts item.title end実行結果
参考にしたページ
- 投稿日:2020-01-26T18:45:02+09:00
Prontoを実行するGithub Actionを作った
Ruby でコードを書くときに Rubocop などでコードチェックするのは品質向上に役立つと思う。
Pronto を使って Rubocop を実行すると Github のプルリクエストにコメントとしてエラーを指摘してくれる。
あんまりいい例でないけどこんな感じ。チーム開発するときには「不要な改行は取ってください」とかレビューでコメントしなくて良くなるし、1人で開発するときにもうっかりミスを防げて良い(私は1人でも実装中のメモを残すためにプルリクエストを作ったりする)。
自動で実行されないと結局使わなくなっていくので、CIサービスなどで実行していたのだけど、Github Workflow で実行できればそれが一番手軽なのでやってみた。
最初は Pronto の README に記載されているGitHub Actions Integration を見ながらワークフローを定義して使っていたのだけど、pronto 等の gem インストールだけで1分以上かかっていた。
予め、 pronto 関連 gem をインストールしたDockerイメージを利用すれば時間短縮できるのでは? と思い立ち、 Github Actionを作った1。結果としてイメージのPullにかかる時間は10秒少々なので時間短縮できた。作ったものは次で公開している。
以下、公開している Pronto Action について紹介する。
サポートしている Pronto runners
次のPronto Runnerを使えるようにしている。
- pronto-brakeman
- pronto-eslint_npm
- pronto-rails_best_practices
- pronto-rubocop
- pronto-scss
- pronto-slim_lint
Runnerはもっとたくさんあるが、Ruby(Rails)でなにか作るときにこれくらいが使えればさしあたって私には事足りると思う。
サポートしている Rubocop extentions
Rubocop は徐々にチェック内容ごとにExtentionとして分割されているが、こちらはできる限り含めるようにした。
- rubocop-i18n
- rubocop-md
- rubocop-minitest
- rubocop-performance
- rubocop-rails
- rubocop-rake
- rubocop-require_tools
- rubocop-rspec
- rubocop-sequel
- rubocop-thread_safety
上記以外に cookstyleもあるのだけど、インストールでエラーが起ったので除外した2。
設定項目
ワークフローを定義するときに Pronto Action に設定できる項目は次の通り。
name reqire default github_token true -- commit false origin/master runner false pronto formatters false github_status github_pr 基本的に Pronto の Usageに記載されているOptionと同じ3。
設定例
ワークフローの設定例を紹介する。
Rubocop を実行する
シンプルに Rubocop を実行するワークフローの定義は次の通り。
The followoing yaml is a simplest workflow difinition of using pronto-action.
.github/workflows/pronto-rubocop.yamlname: Pronto on: pull_request: types: [opened, synchronize] jobs: pronto: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: HeRoMo/pronto-action@v0.4.0 with: github_token: ${{ secrets.GITHUB_TOKEN }}指摘をコメントする場所が必要なので ワークフローをトリガーするイベントは
pull_requestにしておくのがポイント。
あと、Pronto の実行には git の履歴が必要なのでactions/checkout@v1でチェックアウトする4。ESLintを実行する
Actionで利用するDockerイメージには ESLint は含めていないのでセットアップしてから実行する必要がある5。
それらを含めたワークフローの設定例は次の通り。.github/workflows/pronto-eslint_npm.yamlname: Pronto on: pull_request: types: [opened, synchronize] jobs: eslint_npm: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Setup Node.js uses: actions/setup-node@v1 with: node-version: '12.14.x' - name: yarn install run: yarn install - name: pronto run uses: HeRoMo/pronto-action@v0.4.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} runner: eslint_npm多分、これだけだと 「
eslintが見つからない」とかエラーが出ると思う。
そうなったら。.pronto_eslint_npm.ymlというファイルを作って、eslint_npm を設定する6。その設定例は次の通り。
.pronto_eslint_npm.ymleslint_executable: './node_modules/.bin/eslint' files_to_lint: '(\.js|\.ts)$' # TypeScriptをチェックしたいときには指定しておく cmd_line_opts: './src/**/* ./__tests__/**/*' # eslint のコマンドラインオプションまとめ
- Github workflow で Pronto を実行するための Github Action を公開した。
- 時間的なオーバヘッド少なくPronto実行を自動化できる。
- リポジトリのソースにProntoを含めなくても(Gemfileに記述しなくても)、Prontoを実行できる。
Pronto RubyというActionも公開されているが、いろいろと依存関係が解決できてなくて特定のプロジェクトには合うだろうけど、汎用的に使えそうになかったので自作することにした。 ↩
個人的に今後、Chefを使うことはないと思うので、調べる気にもならなかった。 ↩
プルリクエストに対して実行するので、コミット前の変更をチェックするためのオプションである
--staged、--unstagedは不要と思い、除外した。 ↩
actions/checkout@v2がリリースされているがこれはデフォルトfetch-depth:1でチェックアウトするので、prontoが動けない。動くようにしようとすると設定項目が増えてちょっと面倒。 ↩Dockerイメージに含めることもできるのだけど、ESLint周りはアップデートが速すぎて、Actionとして最新を保つのが大変だし、ワークフローで利用する側としてもそれに追随するのは辛いと思うのでプロジェクトの package.json からインストールして利用するようにした。 ↩
https://github.com/doits/pronto-eslint_npm#configuration-of-eslintnpm ↩
- 投稿日:2020-01-26T18:28:58+09:00
実務未経験者がUdemyの講座を参考にして、RailsでInstagram風アプリを作ったので感想をまとめます
現在22歳の大学生です。就職先としてweb形自社開発企業を目指し、ポートフォリオ作成と就活を頑張っています。
今回はUdemyの講座を参考にInstagram風アプリを作成したので、感想などを書いていきます。
参考にした教材
今回はUdemyにある「Beautiful Ruby on Rails Apps in 30 Days & TDD - Immersive」を使用しました。
こちらの教材はInstagram、Evernote、Tumblrなどの有名アプリを含めた、8つのアプリの作り方をRuby on Railsで学ぶという盛りだくさんの内容です。
しかもこの教材、無料です。ただその代わり、教材の内容は2014年ぐらいと少し古いです。
あと解説は英語で日本語字幕などはありません。まあ内容は難しくないので大丈夫です。難易度はProgate以上、Railsチュートリアル未満
Railsチュートリアルができるなら、この教材は難しくありません。ただhamlやscss、新しいgemなどの使い方は覚える必要があります。
逆にProgateレベルの一般的な知識がないと、この教材は難しいと思います。理由は説明が少ないからです。
講座内で解説はありますが、それでも基本を理解していないと追いつけません。作った成果物
講座内で配布されているCSSを使っていないので、UIが貧弱です。
ちなみに講座内で配布されているCSSを使うとこんな感じになるようです。配布場所はここ。
学んだこと
- hamlとscssの使い方
- gemのsimple_formでformが簡単に作れる。
- gemのdeviseでログイン認証が簡単にできる。
- gemのpaperclipで画像の扱いが簡単にできる。
- gemのact_as_votableでlike機能が簡単に実装できる。
不満なこと
- gemを用いて簡単にアプリができてしまったので、学べた感じがしない。
- レベルが高い内容ではない。
まとめ
Instagram風アプリが作りたかったので受講しましたが、そこまでレベルの高い内容ではありませんでした。
ですが無料の教材ということを踏まえれば、内容は充実していたと思います。Instagram風のアプリを作成してみたい方は利用してみてはいかがでしょうか?
- 投稿日:2020-01-26T18:07:18+09:00
Railsのルーティングを支える技術 - Journeyについて
Journeyは、Railsのルーティング処理を担当するライブラリです。Rails3で導入され、大規模なアプリケーションでの処理が高速化しました。
今回は、Journeyがどのようにしてルーティング処理を行っているかを紹介します。
TL;DR
- Railsのルーターは、ウェブから受け取ったURL(入力URL)を認識し、ルート定義と見比べマッチする処理が見つかればそれを呼び出す
- 入力URLに対応する処理を高速に探すためのライブラリがJourneyである
- Journeyはルート定義を非決定性有限オートマトン(NFA)に変換し、マッチングを行う
- Journeyは以下ステップを踏み、ルート定義をNFAに変換している
- ルート定義URLを字句解析し、トークンに分解する
- トークンから抽象構文木(AST)を構築する
- ASTからNFAに変換する
- Journeyは以下ステップを踏み、入力URLに対するマッチング処理を行っている
- 入力として与えられたURLを字句解析し、NFA入力形式に分解する
- 入力をもとにNFAのシミュレーションをする
- NFAが受理状態となった場合、対応する処理を呼び出す
- NFAでのマッチングを行うことで、高速なルーティングを実現している
Journeyを理解するために必要な基本知識
Journeyの内部実装を説明するまえに、まずは理解が必要な項目の解説を行います。
- Railsのルート定義について
- 有限オートマトンとはなにか
Railsのルート定義
Railsでは、以下文法でルーティングの定義を行います。
get "/articles(.:format)", to: "articles#index" get "/articles/new(.:format)", to: "articles#new" get "/articles/:id/edit(.:format)", to: "articles#edit" get "/articles/:id(.:format)", to: "articles#show"これは例えばGETリクエストで
/articles/12が入力URLとして与えられた場合、処理articles#showを呼び出します。
:(コロン)で始まる項目はパラメーターで、URLで利用できる英数字記号ならなんでもよいです。
()で始まる部分は任意の項目です。例えば、/articles/12.jsonはid=12、format=jsonと認識され、articles#showにマッチします。入力URLが複数定義にマッチした場合は、先に定義した処理が呼び出されます。
例えば、入力URL/articles/newはarticles#newとarticles#showにマッチしますが、先に定義をしているnewが呼び出されます。有限オートマトン
Journeyのルートマッチング処理を理解するためには、オートマトンについて理解をしておく必要があります。
オートマトンは、計算理論で使われるモデルのことです。
今回は有限オートマトンと呼ばれる領域に絞って説明します。有限オートマトンと聞くとなんだか複雑そうですが、いたってシンプルなモデルです。有限オートマトンは現在の状態と、入力が与えられた時にどの状態へ遷移するかの規則をもっています。
初期状態から入力値に従って状態遷移し、次の状態に進みます。
すべての入力が終わり、最終的な状態が受理状態であったら、受理したという結果を返します。以下は、シンプルなオートマトンを図にしたものです。
この有限オートマトンは、入力値は0,1いずれかを取ります。q0は初期状態で、q0,q1,q2は各種状態を表しています。二重丸q2は、受理状態を表しています。
このオートマトンに入力
1を与えると、状態はq0からq1に遷移します。さらに入力1を与えると、状態はq1からq2に遷移します。その後入力がなければ、受理された状態となります。二重丸q2まで遷移しなかったものは、受理されず拒否状態となります。上記オートマトンに以下の入力を与えた場合、受理状態となります。
11 011 000000011 110 111以下入力では拒否となります。
000 001 0010 00100有限オートマトンには、決定性と非決定性のものがあります。
上記のオートマトンは、入力に対して遷移先が一意に決まるため、決定性有限オートマトン(DFA; Deterministic Finite Automaton) と呼びます。DFAに対し、入力に対して遷移先が一意に決まらないものは、非決定性有限オートマトン(NFA; Nondeterministic Finite Automaton) と呼びます。
例えば、以下はNFAの例です。このオートマトンに入力
1を与えると、状態はq1とq2に同時に遷移します。
NFAの場合、現在の状態を複数取ります。
さらに入力1を与えると、q1,q4へ遷移します。その後入力がなければ、q4は受理するので受理状態となります。上記オートマトンに以下入力を与えた場合は、受理状態となります。
110 ---> q3 111 ---> q4 0101 ---> q4 01000010 ---> q3以下入力では拒否となります。
0000 0001ここから先は完全に余談ですが、NFAとDFAは互いに同じモデルを表現できます(等価性)。そして任意のNFAはDFAに変換することができます。
NFAは遷移中の状態を複数持たないといけないので、遷移の処理効率が悪くなる可能性があります。
正規表現のエンジンは有限オートマトンを使ってマッチング処理をするものがありますが、それらエンジンではNFAはDFAに変換してから処理をすることがあります。
またDFAの場合、状態を最小化するアルゴリズムも存在します。
ただし、NFA/DFA変換も万能かといえばそうではなくて、DFAにすることで状態が膨大になってしまう可能性もあります。
このあたりの話は割愛します。興味がある方はぜひ調べてみてください。Journeyのルートマッチング処理の流れ
Journeyのルートマッチング処理は非決定性オートマトン(NFA) を用いて行います。
例えば、以下ルート定義を考えます。get "/articles/new(.:format)", to: "articles#new"このURLの定義を、以下6つのトークンに分解(字句解析)します。
/ articles / new . 正規表現[^./?]+ (これは . または / または ? 以外の1文字以上の繰り返しを意味する。?-mix: はrubyの正規表現オプションなので一旦無視してください)トークンに分解後、各種状態のノードとエッジとしNFAを作ります。図に表すと以下状態になります。
URLがマッチした場合(受理状態)は二重丸で表しています。このNFAに対して、例えば以下入力を与えた場合は受理となります。
受理した場合はarticles#newにマッチしたことになります。/ (状態が0から1に遷移) articles (状態が1から2に遷移) / (状態が2から3に遷移) new (状態が3から4に遷移、入力はこれ以上ないので受理)/ (状態が0から1に遷移) articles (状態が1から2に遷移) / (状態が2から3に遷移) new (状態が3から4に遷移) . (状態が4から5に遷移) json (状態が5から6に遷移、入力はこれ以上ないので受理)もう少し複雑なルート定義を見てみます。
例えば以下のような定義があるとします。get "/articles(.:format)", to: "articles#index" get "/articles/new(.:format)", to: "articles#new" get "/articles/:id/edit(.:format)", to: "articles#edit" get "/articles/:id(.:format)", to: "articles#show"この定義をNFAで表すと以下となります。
このNFAに対し、以下入力を与えると、2つの受理状態となります。
/ (状態が0から1に遷移) articles (状態が1から2に遷移) / (状態が2から4に遷移) new (状態が4から6に遷移し受理 かつ 4から7に遷移し受理)状態6で受理した場合は
articles#newを、状態7で受理した場合はarticles#showでマッチしたことを表します。
2つの受理状態となった場合は、先に定義したほうを勝ちとします。JourneyのNFAシミュレーターが http://tenderlove.github.io/fsmjs/ にあります。
入力した文字列に対し、受理したかどうかをシミュレーションできます。このシミュレーターで色々な入力パターンを試すと理解が深まります。Journeyの内部処理を覗く
JourneyがNFAを用いてマッチング処理を行っていることがわかりました。
次はJourneyが実際に行っている内部処理を覗いてみます。Journeyは以下ステップを踏み、ルート定義をNFAに変換しています。
- ルート定義URLを字句解析し、トークンに分解する
- トークンから抽象構文木(AST)を構築する
- ASTからNFAに変換する
また、マッチング処理をする際は、以下ステップを踏みます。
- 入力として与えられたURLを字句解析し、NFA入力形式に分解する
- 入力をもとにNFAのシミュレーションをする
- NFAが受理状態となった場合、対応する処理を呼び出す
ルート定義URLを字句解析し、トークンに分解する
ルート定義
/articles/new(.:format)は、以下6つのトークンに分解(字句解析)します。/ articles / new . 正規表現[^./?]+ (これは . または / または ? 以外の1文字以上の繰り返しを意味する)入力をトークンに分解するのは、スキャナが担当しています。
Journeyの場合、Scannerの定義は https://github.com/rails/rails/blob/v6.0.2/actionpack/lib/action_dispatch/journey/scanner.rb にあります。これは単純で、入力に対して以下のようなトークンに分解します。
pry(main)> scanner = ActionDispatch::Journey::Scanner.new => #<ActionDispatch::Journey::Scanner:0x00007ff1ce901498 @ss=nil> pry(main)> scanner.scan_setup("/articles/:id(.:format)") => #<StringScanner 0/23 @ "/arti..."> pry(main)> scanner.next_token => [:SLASH, "/"] pry(main)> scanner.next_token => [:LITERAL, "articles"] pry(main)> scanner.next_token => [:SLASH, "/"] pry(main)> scanner.next_token => [:SYMBOL, ":id"] pry(main)> scanner.next_token => [:LPAREN, "("] pry(main)> scanner.next_token => [:DOT, "."] pry(main)> scanner.next_token => [:SYMBOL, ":format"] pry(main)> scanner.next_token => [:RPAREN, ")"] pry(main)> scanner.next_token nil定義
/articles/:id(.:format)は、以下トークンとなりました。[:SLASH, "/"] [:LITERAL, "articles"] [:SLASH, "/"] [:SYMBOL, ":id"] [:LPAREN, "("] [:DOT, "."] [:SYMBOL, ":format"] [:RPAREN, ")"]トークンから抽象構文木(AST)を構築する
NFAって、よく見ると木構造みたいなものですよね。
次はNFAに変換する前処理として、トークンを一度木構造に変換します。
この処理はパーサーが担当します。パーサーの定義は https://github.com/rails/rails/blob/v6.0.2/actionpack/lib/action_dispatch/journey/parser.y にあります。
このパーサーは、与えられたルート定義をスキャナーを使いトークンに分解後、ASTを組み立てます。
このパーサーはRacc(yaccのruby版)を使って作られています。Racc(yaccの定義)について、簡単に説明します。
例えば以下定義があるとします。expressions : expression expressions | expression; expression : terminal; terminal : symbol | dot; symbol : SYMBOL; dot : DOT;Raccは
:で区切った右辺と左辺でみます。例えば、expressionsはexpression expressionsまたはexpressionで構成されるという定義です。
expressionは、さらにterminalであると定義されます。
terminalはsymbolまたはdotでできていて、symbolはSYMBOL、dotはDOTであると定義されます。大文字で表した定義が終端となります。Raccはさらに、処理にマッチしたときのアクションを定義できます。右辺の一番右にブロックを書くことでアクションを定義できます。
例えば、以下定義でDOTという定義をすると、DOTにマッチしたらDotのインスタンスにする(マッチした定義はvalで取れる)ということが可能です。dot : DOT { Dot.new(val[0]) }JourneyのRacc定義を見てみましょう。以下のようになっています。
expressions : expression expressions { Cat.new(val.first, val.last) } | expression { val.first } | or ; expression : terminal | group | star ; group : LPAREN expressions RPAREN { Group.new(val[1]) } ; or : expression OR expression { Or.new([val.first, val.last]) } | expression OR or { Or.new([val.first, val.last]) } ; star : STAR { Star.new(Symbol.new(val.last)) } ; terminal : symbol | literal | slash | dot ; slash : SLASH { Slash.new(val.first) } ; symbol : SYMBOL { Symbol.new(val.first) } ; literal : LITERAL { Literal.new(val.first) } ; dot : DOT { Dot.new(val.first) } ;DOTノードがでてきたらDotインスタンスにする、SymbolインスタンスがでてきたらSymbolにする。
expressionsにヒットしたらCatノードにして、ヒットしたexpression(すなわちval[0])とexpressions(すなわちval[1])をCatノードの初期値として渡すといった意味になります。このあたりのRaccの使い方について、詳しくは http://i.loveruby.net/ja/projects/racc/doc/usage.html を参照ください。
/articles/:id(.:format)という定義をパーサーに通してみます。パーサーは内部的にトークンに変換後、以下ASTを構築します。pry(main)> parser = ActionDispatch::Journey::Parser.new => #<ActionDispatch::Journey::Parser:0x00007ff1c7f32698 @scanner=#<ActionDispatch::Journey::Scanner:0x00007ff1c7f32670 @ss=nil>> pry(main)> parser.parse("/articles/:id(.:format)") => #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7f0bbb0 @left=#<ActionDispatch::Journey::Nodes::Slash:0x00007ff1c7f101d8 @left="/", @memo=nil>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7f0bc00 @left=#<ActionDispatch::Journey::Nodes::Literal:0x00007ff1c7f10138 @left="articles", @memo=nil>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7f0bc50 @left=#<ActionDispatch::Journey::Nodes::Slash:0x00007ff1c7f100c0 @left="/", @memo=nil>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7f0bca0 @left=#<ActionDispatch::Journey::Nodes::Symbol:0x00007ff1c7f10020 @left=":id", @memo=nil, @name="id", @regexp=/[^\.\/\?]+/>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Group:0x00007ff1c7f0bd40 @left=#<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7f0bd90 @left=#<ActionDispatch::Journey::Nodes::Dot:0x00007ff1c7f0bef8 @left=".", @memo=nil>, @memo=nil, @right=#<ActionDispatch::Journey::Nodes::Symbol:0x00007ff1c7f0be58 @left=":format", @memo=nil, @name="format", @regexp=/[^\.\/\?]+/>>, @memo=nil>>>>>これを図に表すと、以下となります。
ASTからNFAに変換する
最後にルート定義をASTに変換したものから、NFAの状態遷移表を作成します。
これはGTG::Builder( https://github.com/rails/rails/blob/6-0-stable/actionpack/lib/action_dispatch/journey/gtg/builder.rb )が処理を担当します。pry(main)> ast = parser.parse("/articles/:id(.:format)") => #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7a42fc0 @left=#<ActionDispatch::Journey::Nodes::Slash:0x00007ff1c9974a88 @left="/", @memo=nil>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7a43150 @left=#<ActionDispatch::Journey::Nodes::Literal:0x00007ff1c7a43fb0 @left="articles", @memo=nil>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7a432b8 @left=#<ActionDispatch::Journey::Nodes::Slash:0x00007ff1c7a43dd0 @left="/", @memo=nil>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7a43308 @left=#<ActionDispatch::Journey::Nodes::Symbol:0x00007ff1c7a43a38 @left=":id", @memo=nil, @name="id", @regexp=/[^\.\/\?]+/>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Group:0x00007ff1c7a43510 @left=#<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7a43560 @left=#<ActionDispatch::Journey::Nodes::Dot:0x00007ff1c7a438a8 @left=".", @memo=nil>, @memo=nil, @right=#<ActionDispatch::Journey::Nodes::Symbol:0x00007ff1c7a436c8 @left=":format", @memo=nil, @name="format", @regexp=/[^\.\/\?]+/>>, @memo=nil>>>>> ActionDispatch::Journey::GTG::Builder.new(ast).transition_table => #<ActionDispatch::Journey::GTG::TransitionTable:0x00007ff1c9d07cb8 @accepting={4=>true, 6=>true}, @memos={4=>[nil], 6=>[nil]}, @regexp_states={3=>{/[^\.\/\?]+/=>4}, 5=>{/[^\.\/\?]+/=>6}}, @string_states={0=>{"/"=>1}, 1=>{"articles"=>2}, 2=>{"/"=>3}, 4=>{"."=>5}} >acceptingは受理状態を表しています。この場合、状態4または6に到達すれば受け入れとなります。
@accepting={4=>true, 6=>true},状態遷移は
string_statesとregexp_statesで表しています。
例えば、{0=>{"/"=>1}は 状態0から入力/が来たら1に遷移せよ、ということを表しています。
memosは受理した場合の処理を入れておく変数です。とりあえず今は何も使わないのでnilのままにしておきます。これでNFAが完成しました。図に表すと以下となります。
ルートの定義が複数ある場合はどうしたらよいでしょうか。これは、作成したASTをORノードの子とすることで表現できます。
pry(main)> ast_1 = parser.parse("/articles/:id(.:format)") => 省略 pry(main)> ast_2 = parser.parse("/articles/new(.:format)") => 省略 pry(main)> root = ActionDispatch::Journey::Nodes::Or.new([ast_1, ast_2]) => 省略 pry(main)> ActionDispatch::Journey::GTG::Builder.new(root).transition_table => #<ActionDispatch::Journey::GTG::TransitionTable:0x00007ff1c8f4e4f0 @accepting={4=>true, 5=>true, 8=>true, 9=>true}, @memos={4=>[nil], 5=>[nil], 8=>[nil], 9=>[nil]}, @regexp_states={3=>{/[^\.\/\?]+/=>4}, 6=>{/[^\.\/\?]+/=>8}, 7=>{/[^\.\/\?]+/=>9}}, @string_states={0=>{"/"=>1}, 1=>{"articles"=>2}, 2=>{"/"=>3}, 3=>{"new"=>5}, 4=>{"."=>6}, 5=>{"."=>7}} >上記NFAは図に表すと以下となります。
NFAシミュレーション - 入力URLにマッチする処理を求める
NFAが作れました。あとは入力URLをもとにNFAのシミュレーションをして、受理状態になったら対応する処理を呼び出すと、ルートのマッチング処理は完了です。
シミュレーションの処理は https://github.com/rails/rails/blob/6-0-stable/actionpack/lib/action_dispatch/journey/nfa/simulator.rb#L23 となります。
これは以下ステップを踏み、ルート定義に対応する処理を見つけます。
- 入力として与えられたURLを字句解析し、NFA入力形式に分解する
- 入力をもとにNFAのシミュレーションをする
- NFAが受理状態となった場合、対応する処理を呼び出す
NFA::Simulatorは、先程作成した状態遷移表(TransitionTable)を使い、シミュレーションを行います。
pry(main)> parser = ActionDispatch::Journey::Parser.new => #<ActionDispatch::Journey::Parser:0x00007ff1c7fa3578 @scanner=#<ActionDispatch::Journey::Scanner:0x00007ff1c7fa3550 @ss=nil>> pry(main)> ast_1 = parser.parse("/articles/:id(.:format)") => #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7f78418 @left=#<ActionDispatch::Journey::Nodes::Slash:0x00007ff1c7f78a08 @left="/", @memo=nil>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7f78468 @left=#<ActionDispatch::Journey::Nodes::Literal:0x00007ff1c7f78968 @left="articles", @memo=nil>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7f784b8 @left=#<ActionDispatch::Journey::Nodes::Slash:0x00007ff1c7f788f0 @left="/", @memo=nil>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7f78508 @left=#<ActionDispatch::Journey::Nodes::Symbol:0x00007ff1c7f78850 @left=":id", @memo=nil, @name="id", @regexp=/[^\.\/\?]+/>, @memo=nil, @right= #<ActionDispatch::Journey::Nodes::Group:0x00007ff1c7f785a8 @left=#<ActionDispatch::Journey::Nodes::Cat:0x00007ff1c7f785f8 @left=#<ActionDispatch::Journey::Nodes::Dot:0x00007ff1c7f78760 @left=".", @memo=nil>, @memo=nil, @right=#<ActionDispatch::Journey::Nodes::Symbol:0x00007ff1c7f786c0 @left=":format", @memo=nil, @name="format", @regexp=/[^\.\/\?]+/>>, @memo=nil>>>>> pry(main)> table = ActionDispatch::Journey::GTG::Builder.new(ast_1).transition_table => #<ActionDispatch::Journey::GTG::TransitionTable:0x00007ff1c7f42ed0 @accepting={4=>true, 6=>true}, @memos={4=>[nil], 6=>[nil]}, @regexp_states={3=>{/[^\.\/\?]+/=>4}, 5=>{/[^\.\/\?]+/=>6}}, @string_states={0=>{"/"=>1}, 1=>{"articles"=>2}, 2=>{"/"=>3}, 4=>{"."=>5}} > pry(main)> table.memos[4] = "State4 Match" => "State4 Match" # これはstate4で受け入れた場合のロジックをいれます。 pry(main)> table.memos[6] = "State6 Match" => "State6 Match" # これはstate6で受け入れた場合のロジックをいれます。 [35] pry(main)> simulator = ActionDispatch::Journey::NFA::Simulator.new(table) => #<ActionDispatch::Journey::NFA::Simulator:0x00007ff1c8593848 @tt=#<ActionDispatch::Journey::GTG::TransitionTable:0x00007ff1c7f42ed0 @accepting={4=>true, 6=>true}, @memos={4=>"State4 Match", 6=>"State6 Match"}, @regexp_states={3=>{/[^\.\/\?]+/=>4}, 5=>{/[^\.\/\?]+/=>6}}, @string_states={0=>{"/"=>1}, 1=>{"articles"=>2}, 2=>{"/"=>3}, 4=>{"."=>5}}>> [36] pry(main)> simulator.simulate("/articles/3") => #<ActionDispatch::Journey::NFA::MatchData:0x00007ff1c8edb7e8 @memos=["State4 Match"]> [37] pry(main)> simulator.simulate("/articles/3.json") => #<ActionDispatch::Journey::NFA::MatchData:0x00007ff1c8eafe68 @memos=["State6 Match"]> [38] pry(main)> simulator.simulate("/articles/3/hello") => nil
/articles/:id(.:format)の定義に対し、 入力/articles/3や/articles/3.jsonがマッチしていることがわかります。おわりに
今回Journeyの内部実装を見てみました。一見難しそうに見えますが、NFAについて理解できれば何てことはないです。
私は学生時代、オートマトンの授業はあまり得意ではありませんでした。
今回Journeyの調査にあたり各種テキストを見ていたのですが、そういえばこんなこと勉強したなぁ...くらいでおぼろげな記憶しかありませんでした。
まさかRailsの内部でNFAにお目にかかるとは。CSの基礎知識は大事なんだなあと改めて感じた次第です。
- 投稿日:2020-01-26T17:58:25+09:00
VirtualBox+Vagrantを用いた仮想環境構築を行いHelloWorldを出力するまで
はじめに
VirtualBox+Vagrantを用いた仮想環境構築を学習したく
ドットインストールをベースにVagrant上のファイルをVisual Studio Codeで編集して
HelloWorldを出力するまでのメモです。目標
VirtualBox+Vagrantにて仮想OSを構築する。
Visual Studio Codeを使用して仮想OS上でファイル作成する。
HelloWorldを出力する。手順
VirtualBoxをインストール
https://www.virtualbox.org/wiki/Download_Old_Builds_6_0
Vagrantをインストール
vagrant up ができなかった
Vagrantが最新版のVirtualboxを認識していないとのこと
下記記事を参考に設定ファイルを書き換えました。
[Mac] Vagrant 2.2.6 と Virtualbox 6.1でうまくvagrant up ができなかった時の対処法Visual Studio Code をインストール
https://code.visualstudio.com/
Vagrant上のファイルをVisual Studio Codeで編集
下記記事を参考にしました。
Vagrant上のファイルをVScodeで編集 for Mac(初学者向けの注釈付記レポート)Rubyをインストール
下記記事を参考にしました。
CentOSにrbenv, Rubyをインストールする参考情報
ローカル開発環境の構築 [macOS編]
[Mac] Vagrant 2.2.6 と Virtualbox 6.1でうまくvagrant up ができなかった時の対処法
Vagrant上のファイルをVScodeで編集 for Mac(初学者向けの注釈付記レポート)
CentOSにrbenv, Rubyをインストールする
- 投稿日:2020-01-26T17:33:46+09:00
各言語で、継承の挙動はかなり違うという話
はじめに
この記事は、私が色んな言語でひたすら似たようにクラス継承を書いてみて、実際にどんな値が出力されるのかを調査した結果をまとめたものです。時には既知の言語でも「こんな文法あったんだ」と思いながら、時にはHello Worldから頑張りました。
まとめるのが大変だった割に誰得?という内容ですが、同じことが気になった人のために置いておきます。
いやでも新しい発見があるかもしれないのでとりあえず読んでみてください。
意外と面白い結果になったかもしれません。調べた言語
静的型付け
- Java (Corretto 1.8.0_232)
- C# (3.4.0)
- C++ (11.0.0)
- Scala (2.13.1)
- Kotlin (1.3.61)
- Swift (5.1.3)
動的型付け
- Python (3.7.1)
- Ruby (2.6.5)
- PHP (7.1.32)
- JavaScript (node v12.14.1)
オプショナルな静的型付け
- TypeScript (3.7.2)
- Dart (2.7.0)
調査で使うコード
ある親クラスを子クラスが継承します。
その親クラスと子クラスには、同じ名前のインスタンス変数があります。(クラス変数ではありません)
親クラスにはメソッドがあり、そのメソッドを使うとインスタンス変数をコンソールに出力できます。実行時には、子クラスのインスタンスで、継承した親のメソッドを呼びます。
さぁ親と子どっちのインスタンス変数が出力されるでしょうか……というストーリーです。多分読める人が多いであろうJS(ES6以降)のコードで書くと、こういうコードです。
class SuperClass { constructor() { // ※JSではインスタンス変数は実行時に定義されるので、コンストラクタに書く this.instanceVariable = "SuperClass" // 同じ名前で違う値 } superClassMethod() { console.log(this.instanceVariable) } } class SubClass extends SuperClass { constructor() { super() this.instanceVariable = "SubClass" // 同じ名前で違う値 } } const sub = new SubClass() // 子クラスをインタンス化 sub.superClassMethod() // 継承した親クラスのメソッドを呼ぶ自分が普段使っている言語でどういう値が出力されるかぜひ想像してみてください。
(良し悪しはともかくとして)たまにある書き方なので、自分の得意な言語のものは知っているかもしれません。
でも他の言語でどう動くのかを知っていたら、いつか別の会社や別の案件に入った時、もしくは興味本位で他言語に入門した時に役に立つかもしれません。今回は一応、インスタンス変数のみならず、メソッド(クラスメソッドではないインスタンスのメソッド)で
return "SuperClass"をした時にどうなるのかも見たりしています。
この場合は、親クラスと子クラスに同じ名前のメソッドがあり、親クラスのメソッドでそれを呼ぶ……というシナリオです。どんなコードを書いて調べたかは、
コードを見る(hoge言語)
class Hoge {}こんな風に詳細折りたたみ要素が置いてあるので、クリックして開いてもらえると中身のコードを読むことができます。
何をしているか見やすくするために原則としてコピペを採用したかなりWETなコードですが、それはわざとなのでマサカリを投げないでください。今回のポイント
ご存知の通り、プログラミング言語によって存在する文法は異なりますし、評価の仕方も違います。
今回の結果が変わるポイントは、下記であると思います。
- アクセス修飾子が存在するか。また、どんな種類のものがあるか
- 親クラスと子クラスで同名のインスタンス変数をそもそも定義可能か
- インスタンス変数のオーバーライド、メソッドのオーバーライドがそれぞれ存在するか。
- 継承そのものを、各言語がどう処理しているか
結果発表
Java
エンタープライズシステムの覇者、Java。
Javaにはアクセス修飾子として、privateやprotectedがあります。
privateは、現在のクラスからしか見ることはできません。protectedは、現在のクラスと子クラスからアクセスできます。Javaではインスタンス変数のオーバーライドは存在しません。
なので、メソッドでオーバーライドをした時にどうなるのかも見てみます。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected "SuperClass" インスタンス変数の代わりにprivateなメソッド "SuperClass" インスタンス変数の代わりにprotectedなメソッド(override) "SubClass" オーバーライドは文字通り「上書き」してしまいます。
今回は関係ありませんでしたが、Javaは、子クラス側のメソッドで親クラスのインスタンス変数に
super.instanceVariableのようにしてアクセスすることができます。オーバーライドしないゆえにできる芸当ですね。
コードを見る(Java)
public class JavaSample { public static void main(String[] args) { System.out.println("---- SubClass ----"); SubClass sub = new SubClass(); sub.superClassMethod(); System.out.println("---- SubClassProtected ----"); SubClassProtected subp = new SubClassProtected(); subp.superClassMethod(); System.out.println("---- SubClassGetter ----"); SubClassGetter subg = new SubClassGetter(); subg.superClassMethod(); System.out.println("---- SubClassGetterProtected ----"); SubClassGetterProtected subgp = new SubClassGetterProtected(); subgp.superClassMethod(); } } class SuperClass { // privateのインスタンス変数はサブクラスから参照できない private String instanceVariable = "SuperClass"; public void superClassMethod() { System.out.println(instanceVariable); } } class SubClass extends SuperClass { private String instanceVariable = "SubClass"; } // ------------------------------------------- class SuperClassProtected { protected String instanceVariable = "SuperClass"; public void superClassMethod() { System.out.println(instanceVariable); } } class SubClassProtected extends SuperClassProtected { protected String instanceVariable = "SubClass"; // public void subClassMethod() { // System.out.println(instanceVariable); と書くと、 // System.out.println(this.instanceVariable); と同じ。"SubClass"が出る。 // superをつけると、"SuperClass"と表示させることもできる // System.out.println(super.instanceVariable); // } } // ------------------------------------------- class SuperClassGetter { private String instanceVariable() { return "SuperClass"; } public void superClassMethod() { System.out.println(instanceVariable()); } } class SubClassGetter extends SuperClassGetter { private String instanceVariable() { return "SubClass"; } } // ------------------------------------------- class SuperClassGetterProtected { protected String instanceVariable() { return "SuperClass"; } public void superClassMethod() { System.out.println(instanceVariable()); } } class SubClassGetterProtected extends SuperClassGetterProtected { protected String instanceVariable() { return "SubClass"; } }C#
Javaの次はやっぱりC#。歴史的経緯からかなりJavaに近い文法を持ちます。その結果もまたJavaや、後述するC++と似ていますが、Javaには無いものもあります。
ちなみにC#にはプロパティがあるので、メソッドの代わりにプロパティで書きました。C#には
virtualとoverrideがあります。virtualは、「このメソッドはオーバーライドされる可能性があるぞ」と教える修飾子で、overrideは、ここでメソッドはオーバーライドしているぞと教える修飾子です。そこまではいいのですが、C#には更に、
newという変わった修飾子があります。このnewは、インスタンス生成のときのnew Human()のnewとは別モノです。overrideの代わりにnewをつけることで、子クラスからの呼び出しであっても、親クラスのメソッドを親クラスの文脈のままで評価させることができます。なぜかというと、親クラスのメソッドをオーバーライド(上書き)しているわけではなく、ただ隠しているだけだからです。ややこしいですね。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected "SuperClass" プロパティがprivate "SuperClass" プロパティがprotected(override) "SubClass" プロパティがprotected(new) "SuperClass"
コードを見る(C#)
using System; public class CSharpSample { public static void Main(string[] args) { Console.WriteLine("---- SubClass ----"); var sub = new SubClass(); sub.SuperClassMethod(); Console.WriteLine("---- SubClassProtected ----"); var subp = new SubClassProtected(); subp.SuperClassMethod(); Console.WriteLine("---- SubClassGetter ----"); var subg = new SubClassGetter(); subg.SuperClassMethod(); Console.WriteLine("---- SubClassGetterProtectedOverride ----"); var subgpo = new SubClassGetterProtectedOverride(); subgpo.SuperClassMethod(); Console.WriteLine("---- SubClassGetterProtectedNew ----"); var subgpn = new SubClassGetterProtectedNew(); subgpn.SuperClassMethod(); } } class SuperClass { private string instanceVariable = "SuperClass"; public void SuperClassMethod() { Console.WriteLine(instanceVariable); } } class SubClass : SuperClass { // warning CS0414: The field 'SubClass.instanceVariable' is assigned but its value is never used private string instanceVariable = "SubClass"; } // ---------------------------- class SuperClassProtected { protected string instanceVariable = "SuperClass"; public void SuperClassMethod() { Console.WriteLine(instanceVariable); } } class SubClassProtected : SuperClassProtected { // newはオーバーライドではなく、継承元のインスタンス変数隠しているよと明示している new protected string instanceVariable = "SubClass"; } // ---------------------------- class SuperClassGetter { private string instanceVariable { get { return "SuperClass"; } } public void SuperClassMethod() { Console.WriteLine(instanceVariable); } } class SubClassGetter : SuperClassGetter { private string instanceVariable { get { return "SubClass"; } } } // ---------------------------- class SuperClassGetterProtected { protected virtual string instanceVariable { get { return "SuperClass"; } } public void SuperClassMethod() { Console.WriteLine(instanceVariable); } } class SubClassGetterProtectedOverride : SuperClassGetterProtected { protected override string instanceVariable { get { return "SubClass"; } } } class SubClassGetterProtectedNew : SuperClassGetterProtected { protected new string instanceVariable { get { return "SubClass"; } } }C++
C++は組み込みの現場などで使われることが多いらしいですが私は何も知りません。
C++ナニモワカラナイ……。Javaとともに、C#の元となった言語なだけあって、C#に挙動がとても似ています。
インスタンス変数の代わりにメソッドを使った場合で、かつoverrideしないというのは、C#でいうところのnewと同じ挙動をします。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected "SuperClass" インスタンス変数の代わりにprivateなメソッド "SuperClass" インスタンス変数の代わりにprotectedなメソッド(override) "SubClass" インスタンス変数の代わりにprotectedなメソッド(overrideしない) "SuperClass"
コードを見る(C++)
#include <iostream> class SuperClass { // デフォルトだとprivate(クラス外からアクセス不可になる) std::string instanceVariable = "SuperClass"; public: void superClassMethod() { std::cout << instanceVariable << std::endl; } }; class SubClass : public SuperClass { std::string instanceVariable = "SubClass"; }; // ------------------------------- class SuperClassProtected { protected: std::string instanceVariable = "SuperClass"; public: void superClassMethod() { std::cout << instanceVariable << std::endl; } }; class SubClassProtected : public SuperClassProtected { protected: std::string instanceVariable = "SubClass"; }; // ------------------------------- class SuperClassGetter { std::string instanceVariable() { return "SuperClass"; } public: void superClassMethod() { std::cout << instanceVariable() << std::endl; } }; class SubClassGetter : public SuperClassGetter { std::string instanceVariable() { return "SubClass"; } }; // ------------------------------- class SuperClassProtectedGetter { protected: std::string instanceVariable() { return "SuperClass"; } public: void superClassMethod() { std::cout << instanceVariable() << std::endl; } }; class SubClassProtectedGetter : public SuperClassProtectedGetter { protected: std::string instanceVariable() { return "SubClass"; } }; // ------------------------------- class SuperClassProtectedGetterOverride { protected: virtual std::string instanceVariable() { return "SuperClass"; } public: void superClassMethod() { std::cout << instanceVariable() << std::endl; } }; class SubClassProtectedGetterOverride : public SuperClassProtectedGetterOverride { protected: std::string instanceVariable() override { return "SubClass"; } }; int main() { std::cout << "---- SubClass ----" << std::endl; SubClass sub; sub.superClassMethod(); std::cout << "---- SubClassProtected ----" << std::endl; SubClassProtected subp; subp.superClassMethod(); std::cout << "---- SubClassGetter ----" << std::endl; SubClassGetter subg; subg.superClassMethod(); std::cout << "---- SubClassProtectedGetter ----" << std::endl; SubClassProtectedGetter subpg; subpg.superClassMethod(); std::cout << "---- SubClassProtectedGetterOverride ----" << std::endl; SubClassProtectedGetterOverride subpgo; subpgo.superClassMethod(); return 0; }Scala
関数型の風を取り込んだAltJavaです。
JavaやC#と大きく違うところは、インスタンス変数のオーバーライドができることです。新機能来ました!
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected(override) "SubClass"
コードを見る(Scala)
object ScalaSample { def main(args: Array[String]): Unit = { println("---- SubClass ----") val sub = new SubClass sub.superClassMethod() println("---- SubClassProtected ----") val subp = new SubClassProtected subp.superClassMethod() } } class SuperClass { private val instanceVariable = "SuperClass"; def superClassMethod(): Unit = { println(instanceVariable); } } class SubClass extends SuperClass { private val instanceVariable = "SubClass"; } // ---------------------------- class SuperClassProtected { protected val instanceVariable = "SuperClass"; def superClassMethod(): Unit = { println(instanceVariable); } } class SubClassProtected extends SuperClassProtected { override protected val instanceVariable = "SubClass"; }Kotlin
Scalaに大きな影響を受けたAltJavaです。Androidアプリの開発で多く用いられています。
オーバーライドする予定のインスタンス変数にはopenをつけなくてはならないこと以外、Scalaと同じです。
つまりopenはC++やC#のvirtualみたいなものですね。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected(override) "SubClass"
コードを見る(Kotlin)
fun main(args: Array<String>) { println("---- SubClass ----"); val sub = SubClass(); sub.superClassMethod(); println("---- SubClassOverride ----"); val subo = SubClassOverride(); subo.superClassMethod(); } open class SuperClass { private val instanceVariable = "SuperClass"; fun superClassMethod() { println(instanceVariable); } } class SubClass : SuperClass() { private val instanceVariable = "SubClass"; } // ----------------------------------- open class SuperClassOverride { open val instanceVariable = "SuperClass"; fun superClassMethod() { println(instanceVariable); } } class SubClassOverride : SuperClassOverride() { override val instanceVariable = "SubClass"; }Swift
アプリと言ったらAndroidだけではありません。iOSを忘れて貰っては困ります。
Swiftのprivateはバージョンによって挙動が変わったりするようですが、今回はSwift 5で検証したので、Javaと同じく、クラス内のみ有効で、継承されません。Swiftの
letは再代入を禁止した変数宣言です。JSでいうとconstです。
そしてSwiftのvarは再代入可能な普通の変数宣言です。JSでいうletです。ややこしや。Swiftでは、インスタンス変数(Swiftではプロパティという)を、継承元と同じものを定義することはできません。
代わりにComputed Property(C#でいうプロパティのようなもの)を使うと、オーバーライドすることができます。
privateのようなアクセス修飾子をつけないと、internalというスコープになります。internalはJavaでいうpublicみたいなものです(雑)。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がinternal 定義不可 Computed Propertyがprivate "SuperClass" Computed Propertyがinternal(override) "SubClass"
コードを見る(Swift)
class SuperClass { private let instanceVariable = "SuperClass"; func SuperClassMethod() { print(instanceVariable); } } class SubClass: SuperClass { private let instanceVariable = "SubClass"; } // -------------------------------- class SuperClassInternal { let instanceVariable = "Error"; func SuperClassMethod() { print(instanceVariable); } } class SubClassInternal: SuperClassInternal { // error: cannot override with a stored property 'instanceVariable' // let instanceVariable = "SubClass"; } // -------------------------------- // Computed Propertyにしてgetterを生やす。こうしたら関数扱いなのでoverrideができるようになる。 class SuperClassGetter { // letだと、error: 'let' declarations cannot be computed properties private var instanceVariable: String { get { return "SuperClass" } } func SuperClassMethod() { print(instanceVariable); } } class SubClassGetter: SuperClassGetter { private var instanceVariable: String { get { return "SubClass" } }; } // -------------------------------- // Computed Propertyにしてgetterを生やす。こうしたら関数扱いなのでoverrideができるようになる。 class SuperClassInternalGetter { // letだと、error: 'let' declarations cannot be computed properties var instanceVariable: String { get { return "SuperClass" } } func SuperClassMethod() { print(instanceVariable); } } class SubClassInternalGetter: SuperClassInternalGetter { override var instanceVariable: String { get { return "SubClass" } }; } print("---- SubClass ----"); let sub = SubClass(); sub.SuperClassMethod(); print("---- SubClassInternal ----"); let subi = SubClassInternal(); subi.SuperClassMethod(); print("---- SubClassGetter ----"); let subg = SubClassGetter(); subg.SuperClassMethod(); print("---- SubClassInternalGetter ----"); let subig = SubClassInternalGetter(); subig.SuperClassMethod();Python
ここからは動的型付け言語!
Qiitaのタグ投稿ランキングで1位を取り続けるなど、その人気を盤石なものにしたPythonです。Pythonのクラスで特徴的なのは、メソッドの第一引数にインスタンス自身がやってくるので宣言時には第1引数に
selfと書くところです。別にselfじゃなくても良いのですが、みんなselfと書きます。あとは動的型付け言語らしく、インスタンス変数を動的に生み出すので、クラス宣言直下ではなくコンストラクタの中でインスタンス変数を定義します。ここはRubyもJavaScriptも同じですね。
Pythonにはメンバのアクセス制限がありません。修飾子
privateはありません。
関数名の先頭にアンダースコアを1つつけて「privateだからアクセスしないでね」とする文化です。しかしPythonでは、子クラスと名前が衝突した場合に備えた機能があります。アンダースコアを2つつけると、内部的なメンバ名が変わり(ネームマングリング)、元の変数名に簡単にアクセスできなくすることができます。この機能を使うことで、子クラスで同名のメンバがあった場合でも、実行時に元の親クラスの文脈で結果を評価することができるようになります。
pep8-ja - 命名規約 - メソッド名とインスタンス変数
結果を見てみましょう。
条件 結果 インスタンス変数 "SubClass" インスタンス変数の代わりにメソッド "SubClass" インスタンス変数(アンスコ2つ付き) "SuperClass" インスタンス変数の代わりにメソッド(アンスコ2つ付き) "SuperClass"
コードを見る(Python)
class SuperClass: def __init__(self): self.instance_variable = "SuperClass" def super_class_method(self): print(self.instance_variable) class SubClass(SuperClass): def __init__(self): super().__init__() self.instance_variable = "SubClass" # ------------------------------ class SuperClassPrivate: def __init__(self): self.__instance_variable = "SuperClass" def super_class_method(self): print(self.__instance_variable) class SubClassPrivate(SuperClassPrivate): def __init__(self): super().__init__() self.__instance_variable = "SubClass" # ------------------------------ class SuperClassGetter: def instance_variable(self): return "SuperClass" def super_class_method(self): print(self.instance_variable()) class SubClassGetter(SuperClassGetter): def instance_variable(self): return "SubClass" # ------------------------------ class SuperClassPrivateGetter: def __instance_variable(self): return "SuperClass" def super_class_method(self): print(self.__instance_variable()) class SubClassPrivateGetter(SuperClassPrivateGetter): def __instance_variable(self): return "SubClass" print('---- SubClass ----') sub = SubClass() sub.super_class_method() print('---- SubClassPrivate ----') subp = SubClassPrivate() subp.super_class_method() print('---- SubClassGetter ----') subg = SubClassGetter() subg.super_class_method() print('---- SubClassPrivateGetter ----') subpg = SubClassPrivateGetter() subpg.super_class_method()Ruby
「オブジェクト指向の動的型付け言語といったら?」
という連想クイズをされたプログラマは、真っ先にこの言語を思い浮かべる方も多いのではないでしょうか。Rubyにはメソッドに対して使える
privateがありますが、Javaのような言語のprivateとは挙動が違います。詳しくはレシーバとかを説明しなきゃいけなくなるので書きませんが、とりあえず言えることはprivateがあっても継承先にはそのメソッドが見えるということです。なのでJavaでいうとprotectedの方が近いかもしれません。
[Ruby] privateメソッドの本質とそれを理解するメリットちなみにRubyは変数とメソッドの名前空間が別れているので、メソッドをカッコなしで書いても呼び出せます。
それからメソッドはreturnを書かなくても、最後に評価した式の結果を返します。
@変数名がインスタンス変数です。Rubyでは変数名の1文字目で変数の種別(スコープ)がわかります。
条件 結果 インスタンス変数 "SubClass" インスタンス変数の代わりにメソッド "SubClass" インスタンス変数の代わりにprivateなメソッド "SubClass"
コードを見る(Ruby)
class SuperClass def initialize() @instance_variable = "SuperClass" end def super_class_method p @instance_variable end end class SubClass < SuperClass def initialize() super() @instance_variable = "SubClass" end end # -------------------------------- class SuperClassGetter def instance_variable() "SuperClass" end def super_class_method p instance_variable end end class SubClassGetter < SuperClassGetter def instance_variable() "SubClass" end end # -------------------------------- class SuperClassGetterPrivate def super_class_method p instance_variable end private def instance_variable() "SuperClass" end end class SubClassGetterPrivate < SuperClassGetterPrivate private def instance_variable() "SubClass" end end p '---- SubClass ----' subc = SubClass.new subc.super_class_method p '---- SubClassGetter ----' subg = SubClassGetter.new subg.super_class_method p '---- SubClassGetterPrivate ----' subgp = SubClassGetterPrivate.new subgp.super_class_methodPHP
PHPにはJavaのような
privateやprotectedやpublicがあります。
PythonやRubyやJavaScriptには基本的に無いのに! 羨ましい!
でもJavaとは違って、protectedなインスタンス変数はサブクラスのものを読みに行きます。
そこは他の動的型付け言語と足並みを揃えた動きですね。
条件 結果 インスタンス変数がprivate "SuperClass" インスタンス変数がprotected "SubClass" インスタンス変数の代わりにprivateなメソッド "SuperClass" インスタンス変数の代わりにprotectedなメソッド "SubClass"
コードを見る(PHP)
<?php function println($message) { echo $message.PHP_EOL; } // ---------------------------------------- class SuperClass { private $instanceVariable = "SuperClass"; public function superClassMethod() { println($this->instanceVariable); } } class SubClass extends SuperClass { private $instanceVariable = "SubClass"; } // ---------------------------------------- class SuperClassProtected { protected $instanceVariable = "SuperClass"; public function superClassMethod() { println($this->instanceVariable); } } class SubClassProtected extends SuperClassProtected { protected $instanceVariable = "SubClass"; } // ---------------------------------------- class SuperClassGetter { private function instanceVariable() { return "SuperClass"; } public function superClassMethod() { println($this->instanceVariable()); } } class SubClassGetter extends SuperClassGetter { private function instanceVariable() { return "SubClass"; } } // ---------------------------------------- class SuperClassGetterProtected { protected function instanceVariable() { return "SuperClass"; } public function superClassMethod() { println($this->instanceVariable()); } } class SubClassGetterProtected extends SuperClassGetterProtected { protected function instanceVariable() { return "SubClass"; } } // ---------------------------------------- println("---- SubClass ----"); $sub = new SubClass(); $sub->superClassMethod(); println("---- SubClassProtected ----"); $subp = new SubClassProtected(); $subp->superClassMethod(); println("---- SubClassGetter ----"); $subg = new SubClassGetter(); $subg->superClassMethod(); println("---- SubClassGetterProtected ----"); $subgp = new SubClassGetterProtected(); $subgp->superClassMethod();JavaScript
フロントエンドの覇者です。
プロトタイプベースオブジェクト指向言語という、プログラミング言語Selfから影響を受けた言語です。
ES6以降のJavaScriptではクラスベースの書き方も取り入れているため、extendsして継承もできます。
条件 結果 インスタンス変数 "SubClass" 古い書き方でインスタンス変数 "SubClass" インスタンス変数で、呼び出しメソッドがアロー関数 "SubClass" インスタンス変数の代わりにアロー関数 "SubClass"
コードを見る(JavaScript)
class SuperClass { constructor() { this.instanceVariable = "SuperClass" } superClassMethod() { console.log(this.instanceVariable) } } class SubClass extends SuperClass { constructor() { super() this.instanceVariable = "SubClass" } } // ------------------------ function LegacySuperClass() { this.instanceVariable = "SuperClass"; }; LegacySuperClass.prototype.superClassMethod = function() { console.log(this.instanceVariable) }; function LegacySubClass() { this.instanceVariable = "SubClass"; }; LegacySubClass.prototype = new LegacySuperClass(); // ------------------------ class SuperClassArrow { constructor() { this.instanceVariable = "SuperClass" } superClassMethod = () => { console.log(this.instanceVariable) } } class SubClassArrow extends SuperClassArrow { constructor() { super() this.instanceVariable = "SubClass" } } // ------------------------ class SuperClassGetterArrow { instanceVariable = () => { return "SuperClass" } superClassMethod = () => { console.log(this.instanceVariable()) } } class SubClassGetterArrow extends SuperClassGetterArrow { instanceVariable = () => { return "SubClass" } } // ------------------------ console.log('---- SubClass ----') const sub = new SubClass() sub.superClassMethod() console.log('---- LegacySubClass ----') var lsub = new LegacySubClass() lsub.superClassMethod() console.log('---- SubClassArrow ----') const suba = new SubClassArrow() suba.superClassMethod() console.log('---- SubClassGetterArrow ----') const subga = new SubClassGetterArrow() subga.superClassMethod()TypeScript
静的に型をつけられる、イケてるJavaScriptですね。
union型という、高度な型システムを持つHaskellに見られるような型に近いものがあったり、Conditional TypesやらMapped Typesやら表現力のある型があるのが特徴です。TypeScriptでは
privateな同名のメンバを親クラス子クラスで定義するとコンパイルエラーになります。protectedであれば問題ありません。Swiftとは真逆ですね。
条件 結果 インスタンス変数がprivate 定義不可 インスタンス変数がprotected "SubClass" インスタンス変数の代わりにprivateなメソッド 定義不可 インスタンス変数がprotectedで、呼び出しメソッドがアロー関数 "SubClass" インスタンス変数の代わりにprotectedなアロー関数 "SubClass"
コードを見る(TypeScript)
class SuperClass { private readonly instanceVariable: string constructor() { this.instanceVariable = "Error" } public superClassMethod() { console.log(this.instanceVariable) } } class SubClass extends SuperClass { // instanceVariableで定義しようとすると、 // TS2415: Class 'SubClass' incorrectly extends base class 'SuperClass'. // private readonly instanceVariable: string // constructor() { // super() // this.instanceVariable = "SubClass" // } } // ------------------------ class SuperClassGetter { private instanceVariable() { return "Error" } public superClassMethod() { console.log(this.instanceVariable()) } } class SubClassGetter extends SuperClassGetter { // instanceVariableで定義しようとすると、 // TS2415: Class 'SubClassGetter' incorrectly extends base class 'SuperClassGetter'. // Types have separate declarations of a private property 'instanceVariable'. // private instanceVariable() { // return "SubClass" // } } // ------------------------ class SuperClassProtected { protected readonly instanceVariable: string constructor() { this.instanceVariable = "SuperClass" } public superClassMethod() { console.log(this.instanceVariable) } } class SubClassProtected extends SuperClassProtected { protected readonly instanceVariable: string constructor() { super() this.instanceVariable = "SubClass" } } // ------------------------ class SuperClassProtectedGetterArrow { protected instanceVariable = () => { return "SuperClass" } public superClassMethod = () => { console.log(this.instanceVariable()) } } class SubClassProtectedGetterArrow extends SuperClassProtectedGetterArrow { protected instanceVariable = () => { return "SubClass" } } // ------------------------ // アロー関数版 class SuperClassProtectedArrow { protected readonly instanceVariable: string constructor() { this.instanceVariable = "SuperClass" } public superClassMethod = () => { console.log(this.instanceVariable) } } class SubClassProtectedArrow extends SuperClassProtectedArrow { protected readonly instanceVariable: string constructor() { super() this.instanceVariable = "SubClass" } } console.log('---- SubClass ----') const sub = new SubClass() sub.superClassMethod() console.log('---- SubClassGetter ----') const subg = new SubClassGetter() subg.superClassMethod() console.log('---- SubClassProtected ----') const subp = new SubClassProtected() subp.superClassMethod() console.log('---- SubClassProtectedArrow ----') const subpa = new SubClassProtectedArrow() subpa.superClassMethod() console.log('---- SubClassProtectedGetterArrow ----') const subpga = new SubClassProtectedGetterArrow() subpga.superClassMethod()Dart
Flutterが流行りだしてから急に存在感が出てきた印象な言語です。
Dartではprivateやpublicを書くことはありません。メンバの名前にアンダースコアをつけることでprivateにすることはできますが、ライブラリ外から見えなくするというだけなので、Javaなどのprivateとは違います。
条件 結果 インスタンス変数(アンスコ付き) "SubClass" インスタンス変数 "SubClass" インスタンス変数の代わりにメソッド "SubClass"
コードを見る(Dart)
void main() { print("---- SubClassPrivate ----"); final subp = new SubClassPrivate(); subp.superClassMethod(); print("---- SubClass ----"); final sub = new SubClass(); sub.superClassMethod(); print("---- SubClassGetter ----"); final subg = new SubClassGetter(); subg.superClassMethod(); } class SuperClassPrivate { // アンダースコアをつけるとPrivateになるが、 // privateは同一ライブラリに対して可視のため、ここではサブクラスから普通に見えちゃう final String _instanceVariable = "SuperClass"; void superClassMethod() => print(_instanceVariable); } class SubClassPrivate extends SuperClassPrivate { final String _instanceVariable = "SubClass"; } // ------------------------------------ class SuperClass { final String instanceVariable = "SuperClass"; void superClassMethod() => print(instanceVariable); } class SubClass extends SuperClass { final String instanceVariable = "SubClass"; } // ------------------------------------ class SuperClassGetter { String instanceVariable() => "SuperClass"; void superClassMethod() => print(instanceVariable()); } class SubClassGetter extends SuperClassGetter { String instanceVariable() => "SubClass"; }まとめ
ある程度使い慣れた言語でも知らないことがあったりして、意外と楽しかったです。
挙げた例の中には今回初めて書いた言語もあるので、こんな書き方あるよ! とか、間違ってるよ! とかあったらコメントください。全体的にまとめると、動的型付け言語だとスーパークラスのメソッドを呼んだ時でもサブクラスの文脈でまずは評価させようとするような印象でした。一方の静的型付け言語では、「スーパークラスのメソッドなんだから基本はスーパークラスで評価する。ただしオーバーライドしたらその限りじゃない」という感じでした。
個人的に印象深かったのは、下記の5つでした。
- C#には
newというオーバーライドしない用の修飾子がある- ScalaとKotlinは静的型付け言語なのにインスタンス変数のオーバーライドができる
- Pythonのアンダースコア2つのメンバ名はただの名前じゃなく効果もあった
- PHPには動的型付け言語なのにJavaみたいな
privateがある- TypeScriptは重複する
privateな同名メンバをコンパイル時にエラーにして予期せぬ動作になることを防いでくれる更新履歴
- Pythonのprivateな命名規約の説明について誤りがあったため、修正を行いました。 @shiracamus さんありがとうございました。
- 投稿日:2020-01-26T15:50:38+09:00
gem install ovirt-engine-sdk -v '4.2.4'がインストールできない
gem install ovirt-engine-sdk -v '4.2.4'をbundle install しようとするとエラーが発生する。
エラー内容
Building native extensions. This could take a while...
ERROR: Error installing ovirt-engine-sdk:
ERROR: Failed to build gem native extension.currentdirectory:
/home/vagrant/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/ovirt-engine-sdk-4.2.4/ext/ovirtsdk4c
/home/vagrant/.rbenv/versions/2.4.0/bin/ruby -r ./siteconf20180522-11143-c35v2a.rb extconf.rb
checking for xml2-config... no
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers. Check the mkmf.log file for more details. You may
need configuration options.Provided configuration options:
--with-opt-dir
--without-opt-dir
--with-opt-include
--without-opt-include=${opt-dir}/include
--with-opt-lib
--without-opt-lib=${opt-dir}/lib
--with-make-prog
--without-make-prog
--srcdir=.
--curdir
--ruby=/home/vagrant/.rbenv/versions/2.4.0/bin/$(RUBY_BASE_NAME)
--with-libxml2-config
--without-libxml2-config
--with-pkg-config
--without-pkg-config
extconf.rb:29:in `': The "libxml2" package isn't available. (RuntimeError)To see why this extension failed to compile, please check the mkmf.log which can be found here:
/home/vagrant/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/extensions/x86_64-linux/2.4.0-static/ovirt-engine-sdk-4.2.4/mkmf.log
extconf failed, exit code 1
Gem files will remain installed in /home/vagrant/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/ovirt-engine-sdk-4.2.4 for inspection.
Results logged to /home/vagrant/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/extensions/x86_64-linux/2.4.0-static/ovirt-engine-sdk-4.2.4/gem_make.out解決方法
①sudo yum --enablerepo=epel,remi,rpmforge install libxml2 libxml2-develをインストール
②sudo yum -y install curl をインストール
③sudo yum -y install libcurl libcurl-devel をインストールすると
gem install ovirt-engine-sdk -v '4.2.4'
が通使えるようになり、bundle installをすることができます。
- 投稿日:2020-01-26T14:56:15+09:00
[strong parameters][form_with]投稿にユーザー情報を表示する
概要
目的
プロゲート以外の方法(strong parameters)で投稿詳細に投稿主(ユーザー)情報を記載すること。
(ちなみにプロゲートはこんな感じ)実現したいこと
各action すること(ポイント) new from_withを使って、投稿。 create strong parametersを使用して処理。 show 投稿にユーザー情報を引っ張ってくる。 このような流れで進めていきます。
完成イメージ
画像ではユーザー名、アイコン記載しておりますが、
今回は分かりやすく理解してもらうためにユーザー名だけの表示します。環境
Ruby 2.5.6
Rails 5.2.3前提
- userのデーブルがあり、「name」カラムがあること。
- 投稿モデルは今回commentモデルであること。
- deviseを導入していること。(current_userを使用している為)
strong parametersとは?
一言でいうと
「Web画面上から内部へ流れてきた値を安全に受け取る仕組み」のことです。
もう少し噛み砕いて簡単に説明すると、
入力フォームで記述した情報(名前、メールアドレス等)を安全にバックエンドに渡す仕組みのことです。今回そのストロングパラメーターを使用します。
参考URL
Rails初学者がつまずきやすい「ストロングパラメータの仕組み」
【Rails入門】params使い方まとめnew(form_withを使って投稿)
ここでの目的はfrom_withを使って、投稿出来るようにします。
comments/new.html.erb<%= form_with model: @comment, local: true do |form| %> <%= form.text_area :content,class:"new_text" %> <% end %>このようにform_withを使用して、入力された情報をストロングパラメーターで受け取れるようにします。
controller(strong parametersを使用して処理)
app/controllers/comments_controller.rbdef new @comment = Comment.new end def create @comment = Comment.create params.require(:comment).permit(:content).merge(user_id: current_user.id) end def show @comment = Comment.find(params[:id]) @user = User.find_by(id: @comment.user_id) end★解説★
ここでのポイントはcreateアクションの時に、
newで入力された特定の情報(今回はcontent)しか
情報は受け取りませんよ〜!っていう設定をしています。★ポイント★
(1)ログインしているユーザーIDの読み取り
createアクションを実行した時に入力されたcontent以外にもその時に
ログインしているユーザーIDを受け取る設定しています。
current_userはストロングパラメーターでは適用対象外なので、
mergeメソッドを使用して受け取れるようにしました。(2)ログインしているユーザーIDの受け取り
showアクションを実行した時に、
createアクションで読み取ったユーザーIDを紐付けています。参考URL
【Ruby on Rails】ストロングパラメータって何なの?show(投稿にユーザー情報を引っ張る)
app/views/comments/show.html.erb<%= @user.name%> #名前を表示 <%=@comment.content%> #投稿内容を表示これで投稿にユーザー情報を紐付けることが出来ました!
最後に
まだまだ勉強不足なところがありますので、
アップデートできた知識は追加で記載していきます。もし、何か修正点とかございましたらコメント等
恐縮ですが、宜しくおねがいします。
- 投稿日:2020-01-26T14:45:47+09:00
Ruby on Railsの環境構築やり方 ver2020
はじめに
ここまでProgateでRuby on Railsの学習をしてきたが、やはり上のステップに行くためには自分のPCに環境を構築しなければならないと思い重い腰をあげた。
が、これが思った以上にうまくいかず、かなりの時間がかかってしまった。最初Progateを見ながらやってみたがサーバ立ち上げでうまくいかず、ggるといろいろなサイトでやり方を解説してくれているのを見つけたもののどのサイトも書いてあることがばらばらなところがありしかもどれもうまくいかなかった。なぜだ。
結局、Qiita内にある記事(下記参照)を見ながら現在のバージョンにあわせてやってみることでなんとか構築することができた。
ということで、自分が苦難の末環境構築に成功した方法をメモも兼ねてまとめてみた。PCの環境
Windows10 64bit
参考にさせていただいたページ
https://qiita.com/shita_fontaine/items/aa3f762d7afa20c5d42a
https://teratail.com/questions/221381
ほぼこのサイトの通りですが、バージョン等が多少違ったのでこの記事でもう一度まとめなおしてみます。構築
まずこのページからRubyのインストーラをダウンロードしてくる。
いろんなバージョンが並んでいるが、その中でもひときわ目立つバージョンが推奨バージョンだ。だが、参考にさせていただいた記事いわく、このバージョンではsqliteとごちゃごちゃしてうまくいかない。実際自分もうまくいかなかった。ので、2.4.9をインストールしよう。2つあるが、32bitOSならX86,64bitOSならX64をダウンロード。
ダウンロードしたインストーラを起動し、Rubyをインストールしていく。最初の画面では規約に同意するか聞かれるので"I accept the License."にチェックしNextをクリック。次の画面ではインストール先やオプションについて聞かれるがここは何も触らずInstallをクリック。さらに次の画面ではコンポーネントを選べと言われるのでMSYS2にチェックを入れてNextをクリック。
すると、インストールが開始される。結構長いので雪印コーヒーでも飲みつつ待とう。
この画面になったらインストール完了。Run 'ridk install・・・'にチェックが入っていることを確認しFinishをクリック。
すると、こんなウインドウが出てくる。ENTERをクリックしてMSYS2をインストールしよう。これまた長いのでコーラ飲みつつ文字が流れていくのを眺めていよう。流れが止まり、Which components shall be installed? If unsure press ENTER []と表示されたらもう一度ENTERを押すとコマンドプロンプトが閉じてMSYS2のインストールが完了する。コマンドプロンプトをもう一度開き、以下のコマンドでRubyとRubygemsのバージョンを確認する。筆者の環境では以下のバージョンとなった。C:\Users\ユーザ名>ruby -v ruby 2.4.9p362 (2019-10-02 revision 67824) [x64-mingw32] C:\Users\ユーザ名>gem -v 2.6.14.4続いて、bundlerをインストールしていく。budlerについてはこちらを参照されたい。
以下のコマンドを入力してインストール、バージョン確認まで行う。C:\Users\ユーザ名>gem install bundler Fetching: bundler-2.1.4.gem (100%) Successfully installed bundler-2.1.4 Parsing documentation for bundler-2.1.4 Installing ri documentation for bundler-2.1.4 Done installing documentation for bundler after 13 seconds 1 gem installed C:\Users\ユーザ名>bundle -v Bundler version 2.1.4次に、sqlite3をインストールしていく。こちらから画像で示したファイルをダウンロードしよう。
それぞれを解凍し、sqlite-toolsからsolite3.exeを、splite-dllからsplite3.dllをruby.exeが入っているフォルダ(筆者の環境ではC:\Ruby24-x64\bin、インストール時に設定をいじってないなら多分同じ)にコピペしよう。すると、sqlite3コマンドが使えるようになる。例によってバージョンを確認しておこう。C:\Users\ユーザ名>sqlite3 --version 3.31.0 2020-01-22 18:38:59 f6affdd41608946fcfcea914ece149038a8b25a62bbe719ed2561c649b86d824最後にRailsをインストールしよう。参考にさせていただいた記事では、
>gem install railsによってインストールしているが、このコマンドだと一番最後にERROR: Error installing rails: activesupport requires Ruby version >= 2.5.0.というエラーが出てインストールができない。原因は、
>gem install railsだと最新版のRailsをインストールしようとするものの今自分たちはsqlite3との兼ね合いでRubyのバージョンが古いものをインストールしてあるからである。そこで、以下の順にコマンドを実行していく(最後以外結果は省略)。>gem install sprockets -v3.7.2 >gem install -v "5.0.2" >rails -v Rails 5.0.2これでRailsのインストールは完了となる。仕上げにRailsのサーバを立ち上げる。
以下のコマンドを実行しよう(結果省略)。>mkdir sample >cd sample >rails new TestApp --skip-bundle >cd TestAppすると、アプリのディレクトリが作成されるので、そこに移動し、Gemfileを以下のように編集する。
#Use sqlite3 as the ・・・ gem 'sqlite3','1.3.13' ←,'1.3.13'を付け足す上書き保存し、コマンドプロンプトに戻って
>bundle installを実行しよう。
`>rails server'を実行し、http://localhost:3000にアクセスして以下の画面になれば成功。
お疲れ様でした!!最後に
まじで疲れました。でも、構築できたのはまっさらな状態から一気にできたサブPCのほうで、試行錯誤しまくってごちゃごちゃになったメインPCでは
>gem install -v "5.0.2" ERROR: While executing gem ... (Gem::CommandLineError) Please specify at least one gem name (e.g. gem build GEMNAME)みたいなエラーが出て未だ環境構築できてません。仕方ないからしばらく貧弱なサブPC(2万)でなんとかやりつつメインPCの構築を頑張っていきたいと思います。マジの初心者なので記事中におかしな点などありましたら申し訳ありません。
最後に、参考にさせていただいた記事の筆者の方本当にありがとうございました。
それでは、また。
- 投稿日:2020-01-26T14:45:47+09:00
Ruby on Railsの環境構築やりかた! ver2020
はじめに
ここまでProgateでRuby on Railsの学習をしてきたが、やはり上のステップに行くためには自分のPCに環境を構築しなければならないと思い重い腰をあげた。
が、これが思った以上にうまくいかず、かなりの時間がかかってしまった。最初Progateを見ながらやってみたがサーバ立ち上げでうまくいかず、ggるといろいろなサイトでやり方を解説してくれているのを見つけたもののどのサイトも書いてあることがばらばらなところがありしかもどれもうまくいかなかった。なぜだ。
結局、Qiita内にある記事(下記参照)を見ながら現在のバージョンにあわせてやってみることでなんとか構築することができた。
ということで、自分が苦難の末環境構築に成功した方法をメモも兼ねてまとめてみた。PCの環境
Windows10 64bit
参考にさせていただいたページ
https://qiita.com/shita_fontaine/items/aa3f762d7afa20c5d42a
https://teratail.com/questions/221381
ほぼこのサイトの通りですが、バージョン等が多少違ったのでこの記事でもう一度まとめなおしてみます。構築
まずこのページからRubyのインストーラをダウンロードしてくる。
いろんなバージョンが並んでいるが、その中でもひときわ目立つバージョンが推奨バージョンだ。だが、参考にさせていただいた記事いわく、このバージョンではsqliteとごちゃごちゃしてうまくいかない。実際自分もうまくいかなかった。ので、2.4.9をインストールしよう。2つあるが、32bitOSならX86,64bitOSならX64をダウンロード。
ダウンロードしたインストーラを起動し、Rubyをインストールしていく。最初の画面では規約に同意するか聞かれるので"I accept the License."にチェックしNextをクリック。次の画面ではインストール先やオプションについて聞かれるがここは何も触らずInstallをクリック。さらに次の画面ではコンポーネントを選べと言われるのでMSYS2にチェックを入れてNextをクリック。
すると、インストールが開始される。結構長いので雪印コーヒーでも飲みつつ待とう。
この画面になったらインストール完了。Run 'ridk install・・・'にチェックが入っていることを確認しFinishをクリック。
すると、こんなウインドウが出てくる。ENTERをクリックしてMSYS2をインストールしよう。これまた長いのでコーラ飲みつつ文字が流れていくのを眺めていよう。流れが止まり、Which components shall be installed? If unsure press ENTER []と表示されたらもう一度ENTERを押すとコマンドプロンプトが閉じてMSYS2のインストールが完了する。コマンドプロンプトをもう一度開き、以下のコマンドでRubyとRubygemsのバージョンを確認する。筆者の環境では以下のバージョンとなった。C:\Users\ユーザ名>ruby -v ruby 2.4.9p362 (2019-10-02 revision 67824) [x64-mingw32] C:\Users\ユーザ名>gem -v 2.6.14.4続いて、bundlerをインストールしていく。budlerについてはこちらを参照されたい。
以下のコマンドを入力してインストール、バージョン確認まで行う。C:\Users\ユーザ名>gem install bundler Fetching: bundler-2.1.4.gem (100%) Successfully installed bundler-2.1.4 Parsing documentation for bundler-2.1.4 Installing ri documentation for bundler-2.1.4 Done installing documentation for bundler after 13 seconds 1 gem installed C:\Users\ユーザ名>bundle -v Bundler version 2.1.4次に、sqlite3をインストールしていく。こちらから画像で示したファイルをダウンロードしよう。
それぞれを解凍し、sqlite-toolsからsolite3.exeを、splite-dllからsplite3.dllをruby.exeが入っているフォルダ(筆者の環境ではC:\Ruby24-x64\bin、インストール時に設定をいじってないなら多分同じ)にコピペしよう。すると、sqlite3コマンドが使えるようになる。例によってバージョンを確認しておこう。C:\Users\ユーザ名>sqlite3 --version 3.31.0 2020-01-22 18:38:59 f6affdd41608946fcfcea914ece149038a8b25a62bbe719ed2561c649b86d824最後にRailsをインストールしよう。参考にさせていただいた記事では、
>gem install railsによってインストールしているが、このコマンドだと一番最後にERROR: Error installing rails: activesupport requires Ruby version >= 2.5.0.というエラーが出てインストールができない。原因は、
>gem install railsだと最新版のRailsをインストールしようとするものの今自分たちはsqlite3との兼ね合いでRubyのバージョンが古いものをインストールしてあるからである。そこで、以下の順にコマンドを実行していく(最後以外結果は省略)。>gem install sprockets -v3.7.2 >gem install rails -v "5.0.2" >rails -v Rails 5.0.2これでRailsのインストールは完了となる。仕上げにRailsのサーバを立ち上げる。
以下のコマンドを実行しよう(結果省略)。>mkdir sample >cd sample >rails new TestApp --skip-bundle >cd TestAppすると、アプリのディレクトリが作成されるので、そこに移動し、Gemfileを以下のように編集する。
#Use sqlite3 as the ・・・ gem 'sqlite3','1.3.13' ←,'1.3.13'を付け足す上書き保存し、コマンドプロンプトに戻って
>bundle installを実行しよう。
`>rails server'を実行し、http://localhost:3000にアクセスして以下の画面になれば成功。
お疲れ様でした!!最後に
この試行錯誤はまじで疲れました。。。初心者なので記事中におかしな点などありましたら申し訳ありません。
最後に、参考にさせていただいた記事の筆者の方本当にありがとうございました。
それでは、また。
- 投稿日:2020-01-26T14:27:29+09:00
PAY.JPでクレジットカードを登録する方法
プログラミング初心者が備忘録のために書いていますのでご注意ください。
今回はカードの登録ができるようにします。PAY.JPとは
PAY.JPを使うと簡単にクレジットカード登録ができる。
詳しくはpay.jpのサイトを確認してください。
<PAY.JPのサイトへ>認証
PAY.JPのサイトでユーザー登録。
PAY.JPのAPIを利用するには、ユーザー登録を行い、APIページからAPIキーを取得す必要があります。
ユーザー登録するとテスト用キーと本番用のキーが作成されます。
サイドバーのAPIのボタンを押すと下のページに移動します。
今回はテストのみ実装するのでテスト秘密鍵とテスト公開鍵を使用します。
環境変数に書き込む
秘密鍵はコードに直接書かないので環境変数を設定します。
それぞれのPCによって環境変数の書き方は異なると思いますので注意してください。ターミナル$ vim ~/.bash_profile iでインサートモード export PAYJP_PRIVATE_KEY='テスト秘密鍵' :wqで終了 $ source ~/.bash_profilegemの導入
実装するアプリのGemfileに書き込み、bundle install。
Gemfilegem 'payjp'cardsテーブルの作成
クレジットカードを登録するテーブルを作ります。
すでにusersテーブルができている前提で進めています。ターミナル$ rails g model card*****_create_cards.rb(マイグレーションファイル)class CreateCards < ActiveRecord::Migration[5.2] def change create_table :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ターミナル$ rails db:migrateアソシエーションの設定
1人のユーザーが1つのクレジットカードを持てるようにします。
user.rbhas_one :cardcard.rbbelongs_to :userコントローラーの作成
cards_controllerを作り require を記述。
ターミナル$ rails g controller cardscards_controller.rbrequire 'payjp'ルーティングの設定
新規クレジットカードを登録するためnewとcreateを作成
routes.rbresources :cards, only: [:new, :create]ビューの作成
超単純なビューの例を作りました。
フォームのみ作成したのでご自分の目的に応じて作成してください。
- カード番号 ・ 有効期限(月/年) ・ セキュリティーコード
上記の4点を登録できるようにしました。new.html.haml= form_with model: @card, url: cards_path, local: true, html: { name: 'inputForm' } do |f| カード番号 = f.text_field :card_number, id: "card_number" 有効期限 = f.select :exp_month, [["01",1],["02",2],["03",3],["04",4],["05",5],["06",6],["07",7],["08",8],["09",9],["10",10],["11",11],["12",12]] = f.select :exp_year, [["20",2020],["21",2021],["22",2022],["23",2023],["24",2024],["25",2025],["26",2026],["27",2027],["28",2028],["29",2029]] セキュリティーコード = f.text_field :cvc, id: 'cvc' #card_token = f.submit '登録', id: 'token_submit'またapplication.html.hamlに以下の記述を追加する必要があります。
application.html.haml%head %script{src: "https://js.pay.jp/", type: "text/javascript"}jsの作成
jsを使って、取得したトークンを送信できるようにします。
payjp.jsdocument.addEventListener( "DOMContentLoaded", e => { if (document.getElementById("token_submit") != null) { Payjp.setPublicKey("自分のテスト公開鍵を記述"); let btn = document.getElementById("token_submit"); btn.addEventListener("click", e => { e.preventDefault(); let card = { number: document.getElementById("card_number").value, cvc: document.getElementById("cvc").value, exp_month: document.getElementById("exp_month").value, exp_year: document.getElementById("exp_year").value }; Payjp.createToken(card, (status, response) => { if (status === 200) { $("#card_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 );コントローラーの編集
ビューに合わせてコントローラーを記述します。
save後は移動したいページをご自分で指定してください。
今回はusers_controllerのdoneアクションに飛べるようにしています。cards_controller.rbclass CardsController < ApplicationController require 'payjp' def create Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"] if params['payjp-token'].blank? redirect_to action: "new" else user_id = current_user.id customer = Payjp::Customer.create( card: params['payjp-token'] ) @card = Card.new(user_id: user_id, customer_id: customer.id, card_id: customer.default_card) if @card.save redirect_to controller: :users, action: :done else redirect_to action: "new" end end end end最後に
何とか登録出来ただけなので細かい設定はできていませんが、参考になる方がいればと思います。
間違いがあるかもしれないのでご了承ください。
- 投稿日:2020-01-26T12:44:20+09:00
#Ruby #AWS S3 / upload not UTF-8 ( e.g SJIS ) file / publish presigned url / directly open download file from browser / as text/csv
tempfile = Tempfile.new csv_string = 'あ,い,う' File.write(tempfile.path, csv_string, encoding: Encoding::SJIS) s3_object.upload_file(tempfile.path) s3_object.upload_file( tempfile.path, content_disposition: 'attachment; filename="example.csv"', content_type: 'text/csv' ) tempfile.close tempfile.unlink s3_object.presigned_url(:get, expires_in: 60 * 60)ref
Class: Aws::S3::Client — AWS SDK for Ruby V2
https://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.htmlOriginal by Github issue
- 投稿日:2020-01-26T12:36:15+09:00
【Ruby】メソッドの引数は値渡し?参照渡し?
①〜④でreplaceメソッド実行後の変数aの値はそれぞれ何になるでしょうか。この記事ではメソッドの引数と、メソッド呼び出し元の変数について動きを追って見ていきます。
①破壊的メソッドによる変更
a = "hoge" replace(a) def replace(str) str.upcase! end②破壊的メソッド実行後、値を変更
a = "hoge" replace(a) def replace(str) str.upcase! str = "fuga" end③配列自体を変更
a = ["hoge", "fuga"] replace(a) def replace(list) list = ["piyo", "fuga"] end④配列の要素を変更
a = ["hoge", "fuga"] replace(a) def replace(list) list[0] = "piyo" endメソッドの引数は値渡し?参照渡し?
メソッド内で引数の値を変更すると元の変数の値も変わるのが参照渡し、変わらないのが値渡しです。Rubyのメソッドは値渡しなのか参照渡しなのかを検証するために引数の値をみていきます。
値渡し?
もしメソッドの引数が値渡しであれば、
add(a)実行後のaは2ではなく1のままになるはずです。a = 1 add(a) puts a #=> 1 #変更されていない def add(x) x += 1 endaは2ではなく1になりました。addメソッド実行前後を比較して値が変わらない=メソッドの引数は値渡しであるということがわかります。
参照渡し?
次に、④のメソッド内で配列自体を変更しているケースを考えてみます。メソッドの引数が値渡しであれば、a[0]は"hoge"のまま変わらないはず。
a = ["hoge", "fuga"] replace(a) puts a #=>["piyo", "fuga"] #"piyo"に変わっている def replace(list) list[0] = "piyo" end結果、a[0]は"piyo"になりました。ということはメソッドの引数は参照渡しなのでしょうか?
参照の値渡し
④のケースは参照渡しのように見えますが、実は値渡しです。ただし、値渡しで渡されているのはオブジェクトの参照です。object_idを確認すると、replace実行前のaと
list[0] = "piyo"直前の引数xは同じobject_idになっています。
配列の要素の変更は破壊的メソッドのため、オブジェクトの値そのものが変わり、replace後のaに反映されます。そのため、あたかも参照渡しをしているように見えるのです。a = ["hoge", "fuga"] puts a.object_id #=>70252646834320 replace(a) puts a.object_id #=>70252646834320 # replaceメソッド実行前のaと同じID def replace(list) puts list.object_id #=>70252646834320 # aと同じID list[0] = "piyo" puts list.object_id #=>70252646834320 # aと同じID endでは、なぜ「値渡し?」で確認したケースでは値が変わらなかったのでしょうか。
object_idの動きを確認してみます。
add(a)実行前のaと、x += 1直前のxは同じobject_idになっているので同じオブジェクトを参照しています。
x += 1を実行すると、xがもとの変数aとは別のオブジェクトに変わっています。
x += 1は非破壊的変更のため新しいオブジェクトが生成され、aには値が反映されなかったことがわかります。a = 1 puts a.object_id #=>3 add(a) puts a.object_id #=>3 def add(x) puts x.object_id #=>3 x += 1 puts x.object_id #=>5 #>オブジェクトIDが変わっている end答え合わせ
では、①〜④のメソッド実行後のaの値を見ていきましょう。
①破壊的メソッドによる変更
これは④と同じで、破壊的メソッドによる変更を行なっているため、aの値が大文字の"HOGE"に変わります。a = "hoge" puts a.object_id #=>70252646922080 replace(a) puts a.object_id #=>70252646922080 puts a #=>"HOGE" #aが大文字の"HOGE"に変更された def replace(str) puts str.object_id #=>70252646922080 str.upcase! # strを大文字に変更 puts str.object_id #=>70252646922080 #aと同じID end②破壊的メソッド実行後、値を変更
str.upcase!までの流れは①と同じです。が、②の場合はstr.upcase!の後にstr = "fuga"を実行しています。
str = "fuga"を実行するとオブジェクトIDが変わるため、aにはstr.upcase!までの値が反映され、"HOGE"になります。a = "hoge" puts a.object_id #=>70252647097220 replace(a) puts a.object_id #=>70252647097220 puts a #=>"HOGE" def replace(str) puts str.object_id #=>70252647097220 str.upcase! puts str.object_id #=>70252647097220 str = "fuga" puts str.object_id #=>70252647132520 #オブジェクトIDが変わっている end③配列自体を変更
④はメソッド内で配列の要素を変更する例でしたが、こちらは配列そのものを変更するケースです。list = ["piyo", "fuga"]は破壊的な変更ではないため、新しくオブジェクトが生成され、オブジェクトIDが変わっています。つまり、もとの変数aにはlist = ["piyo", "fuga"]が反映されず、aの値は["hoge", "fuga"]のまま変わりません。a = ["hoge", "fuga"] puts a.object_id #=>70252646933980 replace(a) puts a.object_id #=>70252646933980 puts a #=>["hoge", "fuga"] def replace(list) puts list.object_id #=>70252646933980 list = ["piyo", "fuga"] puts list.object_id #=>70252646967500 #オブジェクトIDが変わっている end④配列の要素を変更
④のオブジェクトIDの動きは上で説明したとおりです。replace(a)後のaの値は["piyo", "fuga"]に変わります。a = ["hoge", "fuga"] puts a.object_id #=>70252646834320 replace(a) puts a.object_id #=>70252646834320 # replaceメソッド実行前のaと同じID puts a #=>["piyo", "fuga"] def replace(list) puts list.object_id #=>70252646834320 # aと同じID list[0] = "piyo" puts list.object_id #=>70252646834320 # aと同じID endまとめ
- Rubyのメソッドの引数は値渡しで参照を渡している(参照の値渡し)
- メソッドの引数に対して非破壊的な変更を行った場合、新しいオブジェクトが生成されるため、メソッドの呼び出し元の変数には値は反映されない
- メソッドの引数に対して破壊的な変更を行った場合、オブジェクトが持つ値自体が変更されるため、メソッドの呼び出し元の変数に値が反映される
参考
- 投稿日:2020-01-26T12:25:25+09:00
[Rails]多対多のモデルの実装、コントローラー、ビューの書き方
はじめに
Railsで多対多のモデルを作り、コントローラー・ビューを実装しました。過去にもやったことがあったので、できるかなーと思ったら、かなり迷ってしまったので、手順をメモしておきます。
データベース設計
今回のテーブルはこんな感じ。作品(works)は複数のカテゴリー(categories)をもち、それぞれのカテゴリーにも複数の作品が属しています。
worksテーブル
column type id integer name string description string ... ... categoriesテーブル
column type id integer name string work_categoriesテーブル(中間テーブル)
column type id integer category_id integer work_id integer ところで、現在、特に中間テーブルに命名規則はないようなのですが(※)。2つのテーブル名を連結した名前をつける場合には、アルファベット順につけるのが慣習のようです。
私は知らずに
work_categoryテーブルという名前にしてしまったので、以下の記述でもそのまま書かせていただきます。。。※
has_and_belongs_to_manyを使っていた頃には命名規則があったみたいです。今は、極力意味のあるテーブル名にするのがルールということです。migrationファイルの記述
中間テーブルのmigrationファイルを作るときには若干ポイントがありました
$ rails g model work_categorymigrationファイルdef change create_table :work_categories do |t| t.references :work, index: true t.references :category, index: true, foreign_key: true t.timestamps end endこの記述で、work_idとcategory_idカラムがそれぞれ生成されます。各項に
index: trueオプションをつけて、検索を高速化。foreign_key: trueオプションをつけて、categoryテーブルにないカテゴリーが追加されないようにしています。ただし、
foreign_key: trueをつけると依存関係にある他のテーブルのカラムを自由に編集できなくなるので、今回の練習用のアプリのように、考えながら作る場合は、最後に外部キー制約をつけても良いのかもしれません。。。▼参考リンク
外部キーの概要と制約を使うことのメリット・デメリットmodelにアソシエーションを追記
work, category, work_categoryのモデルに、それぞれ以下のようにアソシエーションを追記します。
単数形、複数形の気遣いが必要で、Railsガイドの多対多のリレーションの項を参照しました。
models/category.rbclass Category < ApplicationRecord has_many :work_categories #1 has_many :works, through: :work_categories #2 end
#1と#2はこの順番で書かないとエラーが出ます。以下、同じです。models/work.rbclass Work < ApplicationRecord has_many :work_categories, dependent: :destroy has_many :categories, through: :work_categories accepts_nested_attributes_for :work_categories, allow_destroy: true endmodels/work_category.rbclass WorkCategory < ApplicationRecord belongs_to :work belongs_to :category end
dependent: :destroyは、依存関係にあるデータも削除するオプションで、もし、あるworkが削除されたら、それに関連するwork_categoriesテーブルのデータも削除されます。
accepts_nested_attributes_forは、関連する項目も含めて一気に更新、削除する際の設定項目です。
dependent: :destroyがあればいらないのでは??と思うけれども、カテゴリーの内容が変更されたときに、これがあると便利なのだろうか??ちょっと検証が必要な部分です。(後日追記するかもしれません)なお、この辺りに関しては、こちらの記事が大変ためになりました。
▼参照した記事
railsで多対多な関係を実装する時のポイント(加筆修正するかも)controllerのwork_paramsの編集
controllers/works_controller.rbdef work_params params.require(:work).permit(:name, :description, { category_ids: [] }) endWorkを作成(new)・編集(edit・update)するときに、work_categoriesテーブルにデータを入れられるよう、strong parameterに
{ category_ids: [] }でwork_categoriesの配列の入力を許可しています。viewでカテゴリー一覧をチェックボックスすで表示し、編集できるようにする
works_controllerのnewとeditで、categoryをチェックボックスで表示し、チェックを入れるとカテゴリーの値が設定・編集できるようにします。
▼作りたいものはこんな感じ
▼書いたコードはこちら
controllers/works_controller.rb= collection_check_boxes(:work, :category_ids, Category.all, :id, :name ) do |t| =t.label { t.check_box + t.text }こちらのコードの意味に関しては、以前書いた記事があるので、拙著ですがこちらをご参照ください。
【初心者向け】チェックボックスの書き方あれこれ[Ruby][Rails]
さて、以上をもって多対多の関係を持ったモデル・コントローラー・ビューの実装が一通り完成しました。いざやり始めると細かい気遣いが色々必要でしたので、また他のテーブルとのリレーションを組むときに、参照していきたいです。
最後まで読んでくださり、ありがとうございました。
- 投稿日:2020-01-26T12:10:58+09:00
【Rails】 5分でFullCalendar実装する方法
はじめに
Railsアプリケーションは爆速で開発できるところがメリットですので、開発スピードに着目して記事を書きました。
5分でFullCalendarを実装するために、vim だけでも書けるようにファイルパスを全てしましたので、
vimを使ってパスをコピー&ペーストして、ファイル内のコードを変更することができます。プロトタイプを作る時などにスピード重視で作れば一目置かれる存在になれるかもしれませんので、ぜひご活用ください。
やること
scaffoldで作成したEventモデルにFullCalendarライブラリを適用する。
イメージは下図のような感じになります。コマンド
Rails のバージョンは5.2.4を指定しました。
また、scaffold で Eventモデルを作成しています。// Rails アプリケーション作成 $ rails _5.2.4_ new five_min_fullcalendar $ cd five_min_fullcalendar // scaffold で Event モデルを作成 $ rails g scaffold event title:string body:string start_date:datetime end_date:datetime $ rails db:migrate RAILS_ENV=developmentGemFile
GemFile へ3つのgemを追加して
bundle installGemFilegem 'jquery-rails' gem 'fullcalendar-rails' gem 'momentjs-rails'$ bundle installCSS
fullcalendar の部分を追加。
app/assets/stylesheets/application.css*= require_tree . *= require_self *= require fullcalendar */JavaScript
丸々コピーして、
javascripts/application.jsへ貼り付けをしてください。app/assets/javascripts/application.js//= require jquery //= require moment //= require fullcalendar $(function () { // 画面遷移を検知 $(document).on('turbolinks:load', function () { if ($('#calendar').length) { function Calendar() { return $('#calendar').fullCalendar({ }); } function clearCalendar() { $('#calendar').html(''); } $(document).on('turbolinks:load', function () { Calendar(); }); $(document).on('turbolinks:before-cache', clearCalendar); //events: '/events.json', 以下に追加 $('#calendar').fullCalendar({ events: '/events.json', //カレンダー上部を年月で表示させる titleFormat: 'YYYY年 M月', //曜日を日本語表示 dayNamesShort: ['日', '月', '火', '水', '木', '金', '土'], //ボタンのレイアウト header: { left: '', center: 'title', right: 'today prev,next' }, //終了時刻がないイベントの表示間隔 defaultTimedEventDuration: '03:00:00', buttonText: { prev: '前', next: '次', prevYear: '前年', nextYear: '翌年', today: '今日', month: '月', week: '週', day: '日' }, // Drag & Drop & Resize editable: true, //イベントの時間表示を24時間に timeFormat: "HH:mm", //イベントの色を変える eventColor: '#87cefa', //イベントの文字色を変える eventTextColor: '#000000', eventRender: function(event, element) { element.css("font-size", "0.8em"); element.css("padding", "5px"); } }); } }); });JSON File
JSONファイルを作成して、FullCalendarのライブラリへそのJSONファイルを渡すことで、カレンダーを表示することができます。
app/views/events/index.json.jbuilderjson.array!(@events) do |event| json.extract! event, :id, :title, :body json.start event.start_date json.end event.end_date json.url event_url(event, format: :html) endHTML
id が calendar の div 要素を追加すると、ここに fullCalendarが表示されます。
app/views/events/index.html.erb<div id="calendar"></div>turbolinks対策で必要です。
app/views/layouts/application.html.erb<!-- body タグに data-turbolinks 属性を追加 --> <body data-turbolinks="false"> <%= yield %> </body>開発環境で表示してみる
$ rails s次のURLへアクセス。
localhost:3000/events
New Eventをクリックして、イベントを作成。すると下記の画像のように表示されました。
以上です。
まとめ
ものすごいライブラリと出会ってしまうとすぐに開発ができてしまうため、自分が天才になったのではないかと錯覚してしまいます。
あくまでライブラリの力だと思って日々精進しないといけませんね。参考
- GitHub bokmann/fullcalendar-rails
- RailsでFullCalendarを使って予定を表示するまで
- turbolinksチートシート
- 投稿日:2020-01-26T11:40:56+09:00
#Ruby - Tempfile write rewind close unlink – lifecycle
tempfile = Tempfile.new # => #<File:/var/folders/nt/brt3bhdn50v57vqgz5t6xqc40000gn/T/20200125-67882-1mn35v6> tempfile.write('ABC') # => 3 tempfile.read # => "" File.read(tempfile.path) # => "ABC" tempfile.rewind # => 0 tempfile.read # => "ABC" tempfile.close # => nil File.read(tempfile.path) # => "ABC" tempfile.read # IOError: closed stream # from /Users/yumainaura/.rbenv/versions/2.5.3/lib/ruby/2.5.0/delegate.rb:349:in `read' tempfile.unlink # => true File.read(tempfile.path) # TypeError: no implicit conversion of nil into StringOriginal by Github issue
- 投稿日:2020-01-26T11:13:04+09:00
#Ruby で SJIS のCSVファイルを書き込む、読み込む
encodingフラグを使って色々とやる
stringに対しての直接のencodeは必要なさそうFile.write('tmp/sjis.csv', "あ,い,う\nえ,お,か", encoding: Encoding::SJIS) # => 17 File.read('tmp/sjis.csv', encoding: Encoding::SJIS) # => "あ,い,う\nえ,お,か" CSV.read('tmp/sjis.csv', encoding: Encoding::SJIS) # => [["あ", "い", "う"], ["え", "お", "か"]] CSV.open('tmp/sjis2.csv', 'w', encoding: Encoding::SJIS) do |row| row << ['か', 'き', 'く'] row << ['け', 'こ', 'さ'] end # => <#CSV io_type:File io_path:"tmp/sjis2.csv" encoding:UTF-8 lineno:2 col_sep:"," row_sep:"\n" quote_char:"\""> File.read('tmp/sjis2.csv', encoding: Encoding::SJIS) # => "か,き,く\nけ,こ,さ\n" CSV.read('tmp/sjis2.csv', encoding: Encoding::SJIS) # => [["か", "き", "く"], ["け", "こ", "さ"]]Original by Github issue
- 投稿日:2020-01-26T10:14:37+09:00
#Ruby – UTF8 SJIS でのファイル書き込み、読み込みの挙動を試す
# ----------------------------------------------------------- # String のエンコード # ----------------------------------------------------------- # 日本語を文字列としてエンコードした結果 'あ'.encode(Encoding::SJIS) # => "\x{82A0}" # String的にUTf_8にエンコードし直すと、UTf_8に戻る "あ".encode(Encoding::SJIS).encode(Encoding::UTF_8) # => "あ" # ----------------------------------------------------------- # A. UTF8の文字列を UTF_8指定で ファイルに書き込む # ----------------------------------------------------------- File.write('tmp/utf8.txt', 'あ', encoding: Encoding::UTF_8) # => 3 # 当たり前だがUTF_8で読める File.read('tmp/utf8.txt', encoding: Encoding::UTF_8) # => "あ" # SJIS では読めない File.read('tmp/utf8.txt', encoding: Encoding::SJIS) # Encoding::InvalidByteSequenceError: incomplete "\x82" on Windows-31J # ----------------------------------------------------------- # B. SJIS にエンコードした文字列を UTF_8指定で ファイルに書き込む # ----------------------------------------------------------- File.write('tmp/sjis_string.txt', 'あ'.encode(Encoding::SJIS), encoding: Encoding::UTF_8) # => 3 # なぜかUTF_8で読める File.read('tmp/sjis_string.txt', encoding: Encoding::UTF_8) # => "あ" # SJIS では読めない File.read('tmp/sjis_string.txt', encoding: Encoding::SJIS) # Encoding::InvalidByteSequenceError: incomplete "\x82" on Windows-31J # 文字列のStringでんエンコードは無視されて、Aパターンと全く同じ挙動になっているような気がした # File.write がエンコード関係をすべてうまくハンドリングしてくれているのかもしれない # ----------------------------------------------------------- # UTF_8の文字列を SJIS指定でファイルに書き込む # ----------------------------------------------------------- File.write('tmp/sjis_encoding.txt', 'あ', encoding: Encoding::SJIS) # => 2 # UTF8で読むとこのような文字列が返る File.read('tmp/sjis_encoding.txt', encoding: Encoding::UTF_8) # => "\x82\xA0" # 文字列自体をUTF_8エンコードしても何も変わらない # 何か自分がエンコードやデコードの関係を根本的に理解していない気はする File.read('tmp/sjis_encoding.txt', encoding: Encoding::UTF_8).encode(Encoding::UTF_8) # => "\x82\xA0" # SJIS指定をしてファイルを読んだ時にUTF_8の文字列が返る File.read('tmp/sjis_encoding.txt', encoding: Encoding::SJIS) # => "あ"Original by Github issue
- 投稿日:2020-01-26T09:15:33+09:00
#Ruby の tr でバックスラッシュと他の文字が同時に置換できない?
Wanna replace in string
Repladce backslash with A
Repladce colon with Xputs ' \\ : ' # \ : # Only backslash replace # It works puts ' \\ : '.tr('\\', 'A') # A : # Replace backslash and other calacter one time # It does not work puts ' \\ : '.tr('\\:', 'AX') # \ A # Above case maybe same as this # Only colon replacing puts ' \\ : '.tr(':', 'AX') # \ A # Use expression triple backslash as backslash # It works puts ' \\ : '.tr('\\\:', 'AX') # A X # It works # backslash at last character in tr arg puts ' \\ : '.tr(':\\', 'XA') # A XOriginal by Github issue
- 投稿日:2020-01-26T08:46:41+09:00
#Ruby tr replace backslash and other character netime
Wanna replace in string
Repladce backslash with A
Repladce colon with Xputs ' \\ : ' # \ : # Only backslash replace # It works puts ' \\ : '.tr('\\', 'A') # A : # Replace backslash and other calacter one time # It does not work puts ' \\ : '.tr('\\:', 'AX') # \ A # Above case maybe same as this # Only colon replacing puts ' \\ : '.tr(':', 'AX') # \ A # Use expression triple backslash as backslash # It works puts ' \\ : '.tr('\\\:', 'AX') # A X # It works # backslash at last character in tr arg puts ' \\ : '.tr(':\\', 'XA') # A XOriginal by Github issue
- 投稿日:2020-01-26T06:45:29+09:00
写真整理のためRubyでファイル名変更と年月フォルダへ移動
はじめに
過去、撮りためた写真ファイル、行き当たりばったりに保管してきたのでフォルダ構成が汚い。また、カメラのメーカーごとにファイルの命名規則もバラバラでしたので、整理してみました。
ファイル名をyymmdd_HHMMSS_連番.jpgに変更し、
YYMMフォルダに移動します。環境
- Ubuntu 18.04.3 LTS
- ruby 2.5.1
ソースコード
require 'fileutils' #------------------------------------------------- # カレント配下のファイル※1に対し以下を行う # * ファイル名変更 yymmdd_HHMMSS_連番.jpg ※2 # * pic/年月フォルダへ移動 # # ※1 jpg, jpeg, JPG # ※2 連番はファイル名がかぶっている場合に振る #------------------------------------------------- cur = Dir.pwd + '/' i = 0 # 連番用 j = 0 # 連番用 # 画像の移動先フォルダを作成 if !Dir.exist?('pic') Dir.mkdir('pic') end # 画像以外の移動先フォルダを作成 if !Dir.exist?('other') Dir.mkdir('other') end #------------------------------------------------- # ファイル名を変更しpic/年月へ移動 #------------------------------------------------- # カレント以下を再帰的に処理 Dir.glob('./**/*').each do |f| ext = File.extname(f) # 拡張子取得 # 画像 if ext == '.JPG' || ext == '.jpg' || ext == '.JPEG' || ext == '.jpeg' # 移動先ディレクトリ作成 to_dir_name = cur + '/pic/' + File.mtime(f).strftime('%y%m') if !Dir.exist?(to_dir_name) Dir.mkdir(to_dir_name) end # 変更名を作成 new_name = to_dir_name + '/IMG_' + File.mtime(f).strftime('%y%m%d_%H%M%S') + ext if File.exist?(new_name) # 移動先に存在する場合、連番を振る i +=1 new_name = to_dir_name + '/IMG_' + File.mtime(f).strftime('%y%m%d_%H%M%S') + '_' + i.to_s + ext end # 画像以外 else # 移動先ディレクトリ作成 to_dir_name = cur + '/other' # 変更名を作成 new_name = to_dir_name + '/' + File.mtime(f).strftime('%y%m%d_%H%M%S') + ext if File.exist?(new_name) j +=1 # 移動先に存在する場合、連番を振る new_name = to_dir_name + '/' + File.mtime(f).strftime('%y%m%d_%H%M%S') + '_' + j.to_s + ext end end # 移動 if FileTest.file? f if File.basename(f) == 'move_files.rb' || File.basename(f) == 'select_files.rb' else # カレントディレクトリを変更(ブロックを抜けると戻る) FileUtils.chdir(File.dirname(f)) { # ファイルを移動 File.rename(File.basename(f), new_name) } end end end puts '--------------------' puts 'done.' puts '--------------------'おまけ
ちなみに、上でYYMMに整理したのは年月ごとに数枚、ランダムに写真をチョイスしたかったからです。大量の過去の写真を振り返りたいという思いでした。
require 'fileutils' #------------------------------------------------- # フォルダごとにn枚選ぶ #------------------------------------------------- cur = Dir.pwd + '/' dir_arr = Array.new file_arr = Array.new # フォルダを作成 if !Dir.exist?('select') Dir.mkdir('select') end # フォルダごとに取得するファイル数 if ARGV[0] == nil n = 3 else n = ARGV[0].to_i end # ディレクトリ一覧を作成 # カレント以下を再帰的に処理 Dir.glob('./**/*').each do |f| if FileTest.directory? f if f == './select' else dir_arr << f end end end # ディレクトリ分ループ dir_arr.each { |d| file_arr.clear # ディレクトリごとの画像ファイル一覧作成 Dir.glob(d + '/*').each do |f| ext = File.extname(f) # 拡張子取得 if ext == '.JPG' || ext == '.jpg' || ext == '.JPEG' || ext == '.jpeg' file_arr << f end end ln = file_arr.length if ln == 0 else file_arr.sample(n).each do |f| new_name = cur + '/select/' + File.basename(f) FileUtils.cp(f, new_name) end end }私は普段コボラーをやっておりますが、Rubyってそんな私でも理解しやすく、プログラマーにとって書くのが楽しい言語だなって思います。そして大変便利です。
- 投稿日:2020-01-26T05:12:12+09:00
irbのJupyter wrapper kernelをpoetryを使ってPyPIで公開する
タイトルの作業を行っていきます。
irbはRubyのREPLです。先に出来上がったものを見せると
https://github.com/PyDataOsaka/irb_kernel
になります。ここに至るまでに下記を行いました。
poetryでパッケージソースの雛形を作成
pip install poetry poetry new irb_kernel cd irb_kernel poetry add pexpect poetry add notebookirb_kernel下にコードを追加
@antimon2 さんの Jupyter の Egison 簡易カーネルを自作してみた。
と
https://github.com/takluyver/bash_kernel
を真似ました。https://github.com/PyDataOsaka/irb_kernel/blob/master/irb_kernel/install.py
は
kernel.jsonをインストールするコマンド用の雛形的コードで
https://github.com/takluyver/bash_kernel/blob/master/bash_kernel/install.py
のkernel_json = {"argv":[sys.executable,"-m","bash_kernel", "-f", "{connection_file}"], "display_name":"Bash", "language":"bash", "codemirror_mode":"shell", "env":{"PS1": "$"} }を
kernel_json = {"argv": [sys.executable,"-m","irb_kernel", "-f", "{connection_file}"], "display_name": "Irb", "language":"ruby", "codemirror_mode": "ruby" }に置き換えただけです。
(kernel.jsonがインストールされるとJupyter NotebookのNewドロップダウンリストにdisplay_nameのvalueであるIrbが追加されます。)次にkernelが行うことを記述するkernel.pyを追加します。
kernel.pyに関してはbash_kernelのkernel.pyより @antimon2 さんの Jupyter の Egison 簡易カーネルを自作してみた。
の egison-kernel.py を真似ています。
egison-kernel.pyの肝の箇所はself.egisonwrapper = replwrap.REPLWrapper("egison --prompt " + prompt, unicode(prompt), None)です。
irb_kernelのkernel.pyでは上記をself.irbwrapper = replwrap.REPLWrapper("irb --simple-prompt", ">> ", None)に変えています。
https://github.com/PyDataOsaka/irb_kernel/blob/master/irb_kernel/__init__.py
と
https://github.com/PyDataOsaka/irb_kernel/blob/master/irb_kernel/__main__.py
は
egison-kernel.py同様のkernel.py から bash_kernel同様に__init__.pyと__main__.pyを分離しただけのものです。これでコードの追加は完了です。
パッケージのビルドとPyPI での公開
# irb_kernel ディレクトリで実行します。 poetry build poetry publishこれだけで完了です。
https://pypi.org/project/irb-kernel/PyPIからのパッケージのインストールとkernel.jsonのインストール,そしてJupyterの起動
sudo apt install ruby pip install irb_kernel # 下記コマンドはパッケージソースのディレクトリのひとつ上では実行しない方がいいです。 python -m irb_kernel.install jupyter notebook以上ですべての作業が終わりました。
インストールすることなくirb_kernelの動作を試されたい場合は
https://mybinder.org/v2/gh/PyDataOsaka/irb_kernel/master
をクリックしてください。最後に
実は irb_kernel には複数行のセルの実行でハングするという問題があります。
おそらくREPLWrapperのオプションcontinuation_promptを設定するといいんじゃないかなと考えていますがまだ行えていません。
もし興味のある方はぜひ開発を進めてください。
- 投稿日:2020-01-26T02:00:15+09:00
困ったらググれ
どうも、momotaroです。初めまして。
TECH::EXPERT受講初めたことがきっかけで、ブログはじめました。
自己学習(アウトプット)の為に投稿していきます。今週月曜からプログラミング初心者として学習を始めて、この一週間で学んだ大きなこと。
それはタイトルにある通り、分からないコードが出てきたり、エラーが出てきたらGoogleさんに遠慮なく質問しよう!
・・・ということですね。
これはTECH::EXPERTのカリキュラムにもきちんと書いてあります。
この教えが意味するものは
『”自己解決能力”を身につける』
だと思います。大切なのは、なぜその問題が起こるのかを理解すること。そして大事なのは、いかにその問題をいち早く解決できるか・・・。
実際現場の方でもググることはよくあるそうです。なので、これからは良い検索方法やわかりやすい解説サイト、ブログなどあれば模索していきたいと思います。
また自分が新しい解説など思いついたり発見した場合は発信していきます。
最後に自分と同じくTECH::EXPERT受講はじめたばかりの人に読んで欲しいブログのリンク載せておきます。
もしよかったら『いいね』押してください。
- 投稿日:2020-01-26T01:37:08+09:00
RSpecで利用するFactoryBotの導入
FactoryBot
FactoryBotはテスト用データの作成をサポートするgemであり、利用するとテスト用のデータを簡単に作成し、テストから呼び出すことができる。
早速Gemfileに'factory_bot_rails'を記述しbundle install を実行しよう
# Gemfile group :development, :test do gem 'rspec-rails' gem 'factory_bot_rails', '~> 4.11' endFactoryBotでテストデータを作成できるよう準備
Userのファクトリを作成
一例としてuserのファクトリを作成していきます。
# spec/factories/users.rb FactoryBot.define do factory :user do sequence(:email) { |n| "tester#{n}@example.com" } #重複を防ぐためsequenceを使う password { 'password' } password_confirmation { 'password' } end endsequenceでデータを生成する毎に通し番号をふってユニークな値を作るようにする。
これが無いとuserが複数回生成された時にemailが重複してしまう。
知らなかった時苦戦した。。。実際にデータを作成
※ RSpecのコードを意識してletで定義している
#specを書くファイル let(:user) { FactoryBot.create(:user) }letで定義したuserが呼び出されるとファクトリ通りのuserのデータが作成される。
省略形
# spec/rails_helper.rb RSpec.configure do |config| config.include FactoryBot::Syntax::Methodsこうすることで、、、
{ create(:user) }FactoryBotを省略してもデータを作成可能。
短いコードで書けるのは良いですよね以上です!























