- 投稿日:2019-05-29T23:40:54+09:00
Rspecで突然謎のエラーが
circleCIで通常通り動いていたテストコードが突然動かなくなりましたが解決したのでその方法を書いていきます。
開発環境
Ruby 2.6.2
Rails 5.2.3
Docker 18.09.2
docker-compose 1.23.2今回発生したエラー
Selenium::WebDriver::Error::UnknownError: unknown error: session deleted because of page crash from unknown error: cannot determine loading status from tab crashed原因
エラーを調べたところ日本語での記事が少なく詳しくはわからなかったのですが、メモリ不足によってクラッシュしてしまうとのこと。
ブラウザのサイズを小さくすれば解決するとの記事を見て試してみたのですが、僕の場合小さくしても解決しませんでした。解決方法
rails_helperのchromeOptionsのところに
--no-sandbox
と--disable-dev-shm-usage
というargumentsを追加してください。--disable-dev-shm-usage
と書くことでChromeは/tmpディレクトリを代わりに使用するようになるみたいです。メモリの代わりにディスクが使用されるので、実行が遅くなる場合があるみたいですがそんなに変わってない気がします。spec/rails_helper.rbCapybara.register_driver :selenium_remote do |app| driver = Capybara::Selenium::Driver.new( app, browser: :remote, desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome( chromeOptions: { args: [ 'window-size=500,500', #念の為サイズも小さく 'headless', '--no-sandbox', # crush回避 '--disable-dev-shm-usage' # crush回避 ] } ), url: 'http://chrome:4444/wd/hub', ) endこれで解決すると思います。
もし間違いがあれば訂正お願いいたします。
参考サイト
https://qiita.com/jb-vasseur/items/d3aa33e08ebba3b9231e
https://stackoverflow.com/questions/53902507/unknown-error-session-deleted-because-of-page-crash-from-unknown-error-cannot
- 投稿日:2019-05-29T23:30:11+09:00
【1分で読める】プログラミングスクールに通って1ヶ月で学んだこと(できるようになったこと)、感じたこと
WEBエンジニアに転向するため2019年5月から某プログラミングスクールに通っています。執筆時点で約1ヶ月経過したので、学んだことやら、感じたことやらを書き記します。スクール自体はあと数ヶ月続きます。
プログラミングスクールに通うか迷っている人の参考になればと思います。(進捗はあくまでも自分の場合です!周りを見ると、進捗は本当にバラバラ)
なんでプログラミングスクールに入ったのかーとか諸々は卒業後にまたまとめて書こうかなと!
スペックとか
- 言語:Ruby
- 年齢:29
- 性別:男
- 5月からプログラミングスクールに通ってるけど、4月15日から事前に学習をスタート
- プログラミング歴はなし!学生時代に授業でやったくらい
- 勉強時間は7〜10時間/日くらい。頭が疲れたら、基本的にやらない感じ
学んだこと(できるようになったこと)
Ruby(Rails)を使ってtwitter,facebook,instagramのようなアプリは作れるようになった!!!(コードのコピペじゃない!)
投稿機能はもちろん、投稿のお気に入り機能、ユーザのフォローフォロワー機能を実装したり、LINEみたいなメッセージのやりとり機能を実装したりもできるようになった。Ajaxを使ったりも。
さらにherokuを使えば簡単に全世界に公開もできる。すごすぎるぞ、自分!!
デザインとか、サーバのこととか、セキュリティとか考えるとまだまだかもしれないけど、1ヶ月半くらいで、railsアプリ開発の基本的なことはできるようになったのかなと勝手に思ってます。
あとは、リファクタリングとかコーディング規約を守るとか頑張らないとなーって感じ。
(本当はもっとあるんだろうな…)感じたこと
- 想像していたものがブラウザ上で形になるのは楽しい!嬉しい!
- 上っ面のところしか理解できていない感がとてもある
- 一人で黙々とやっていたらこんなにハイペース?で勉強を進められないだろうなと実感
→同期、メンターの存在のおかげで頑張ろって思えるし、分からないことも教えあえる。また、一人じゃ気付けなさそうなことも会話から気づきを得ることができる。感謝感謝。
一人ならダラダラと勉強しちゃいそう。。これからのこと
- サーバ(AWS)のことや、フロント(Vue.js)のことを学んでいくぞい!
- RubyやRailsも知れば知るほど、新たな疑問が生まれるのでもっともっと理解を深めていくぞい!
- 就活も始めるぞい!
乞うご期待!
- 投稿日:2019-05-29T23:22:19+09:00
様々なプログラミング言語でクラス (あるいはクラスもどき) を書く
概要
- 様々なプログラミング言語でクラス (あるいはクラスもどき) を書く
Java
MyCounter.java
public class MyCounter { public static MyCounter globalCounter = null; private String name = "No Name"; private int count; public MyCounter(int initValue) { count = initValue; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int addCount(int number) { count += number; return count; } public void print() { System.out.println(format()); } private String format() { return name + ": " + count; } public static void createGlobalCounter(String name, int initValue) { globalCounter = new MyCounter(initValue); globalCounter.setName(name); } }MyMain.java
public class MyMain { public static void main(String[] args) { MyCounter counter = new MyCounter(0); counter.print(); counter.setName("John Doe"); String currentName = counter.getName(); System.out.println("Current Name=" + currentName); int currentValue = counter.addCount(10); System.out.println("Current Value=" + currentValue); counter.print(); MyCounter.createGlobalCounter("CafeBabe! Write once, run anywhere", 256); MyCounter.globalCounter.print(); } }実行結果
No Name: 0 Current Name=John Doe Current Value=10 John Doe: 10 CafeBabe! Write once, run anywhere: 256JavaScript
my_counter.js
class MyCounter { constructor(initValue) { this._name = 'No Name'; this.count = initValue; } get name() { return this._name; } set name(name) { this._name = name; } addCount(number) { this.count += number; return this.count; } print() { console.log(this._format()); } _format() { return this._name + ': ' + this.count; } static createGlobalCounter(name, init_value) { MyCounter.global_counter = new MyCounter(init_value); MyCounter.global_counter.name = name; } } module.exports = MyCounter;my_main.js
const MyCounter = require('./my_counter'); counter = new MyCounter(0); counter.print(); counter.name = 'John Doe'; const currentName = counter.name; console.log('Current Name=' + currentName); const currentValue = counter.addCount(10); console.log('Current Value=' + currentValue); counter.print(); MyCounter.createGlobalCounter('JavaScript was born from Netscape Navigator 2.0', 256); MyCounter.global_counter.print();実行結果
$ node my_main.js No Name: 0 Current Name=John Doe Current Value=10 John Doe: 10 JavaScript was born from Netscape Navigator 2.0: 256Python
my_counter.py
class MyCounter: global_counter = None def __init__(self, init_value): self.__name = "No Name" self.__count = init_value @property def name(self): return self.__name @name.setter def name(self, name): self.__name = name def add_count(self, number): self.__count += number return self.__count def print(self): print(self.__format()) def __format(self): return "{0}: {1}".format(self.__name, self.__count) @classmethod def create_global_counter(cls, name, init_value): cls.global_counter = MyCounter(init_value) cls.global_counter.name = namemy_main.py
from my_counter import MyCounter counter = MyCounter(0) counter.print() counter.name = "John Doe" current_name = counter.name print("Current Name={}".format(current_name)) current_value = counter.add_count(10) print("Current Value={}".format(current_value)) counter.print() MyCounter.create_global_counter("Batteries included. Beautiful is better than ugly.", 256) MyCounter.global_counter.print()実行結果
$ python my_main.py No Name: 0 Current Name=John Doe Current Value=10 John Doe: 10 Batteries included. Beautiful is better than ugly.: 256Ruby
my_counter.rb
class MyCounter attr_accessor :name @@global_counter = nil def initialize(init_value) @name = 'No Name' @count = init_value end def add_count(number) @count += number end def print puts format end private def format "#{@name}: #{@count}" end def self.create_global_counter(name, init_value) @@global_counter = self.new(init_value) @@global_counter.name = name end def self.global_counter @@global_counter end endmy_main.rb
require './my_counter' counter = MyCounter.new(0) counter.print counter.name = 'John Doe' current_name = counter.name puts "Current Name=#{current_name}" current_value = counter.add_count(10) puts "Current Value=#{current_value}" counter.print MyCounter.create_global_counter('Everything is an object in Ruby', 256) MyCounter.global_counter.print$ ruby my_main.rb No Name: 0 Current Name=John Doe Current Value=10 John Doe: 10 Everything is an object in Ruby: 256
- 投稿日:2019-05-29T22:53:44+09:00
Ruby/Ruby on rails/MysqlをDockerで環境構築
DockerでRuby/Ruby on rails/mysqlの環境構築からデプロイまでできたのでメモしておきます。
(1)Dockerfile作成
DockerfileFROM ruby:2.5.3 RUN apt-get update -qq && \ apt-get install -y build-essential \ libpq-dev \ nodejs RUN mkdir /app_name ENV APP_ROOT /app_name WORKDIR $APP_ROOT ADD ./Gemfile $APP_ROOT/Gemfile ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock RUN bundle install ADD . $APP_ROOT(2)docker-compose.yml作成
docker-compose.ymlversion: '3' services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: root ports: - "3306:3306" web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/app_name ports: - "3000:3000" links: - db(3)Gemfile作成
Gemfilesource 'https://rubygems.org' gem 'rails', '5.2.2'Gemfile.lockも作成
$ touch Gemfile.lock(4)新しいアプリを作成
$ docker-compose run web rails new アプリ名 . --force --database=mysql --skip-bundledatabese.ymlを編集
database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password host: dbGemfileをbundle install
$ docker-compose run web bundle install(5)コンテナをビルドと起動
$ docker-compose build # コンテナをビルド $ docker-compose up # コンテナの一斉起動※bundle installの後にbuildする
(6)DB作成
$ docker-compose run web rails db:createDBを作らないとエラーが出る
DBを作成後localhost:3000で初期画面が表示される!
()Herokuでデプロイ
herokuの登録,heroku cliはインストール済みとする
Herokuにアプリ作成
$ heroku createデータベースをMySQLに変更
$ heroku addons:add cleardbClearDBアドオンとは,ClearDBというデータベースサービスが提供している,MySQLを使うためのもの。
$ heroku config | grep CLEARDB_DATABASE_URL
mysql:// 〜 reconnect=true
がデータベース情報
mysql
の部分をmysql2
に変更環境変数を変更
$ heroku config:set DATABASE_URL=mysql2:// 〜 reconnect=true //先ほどのデータベース情報全部$ git add . $ git commit -m "update for upload to heroku"アプリをデプロイ
$ git push heroku masterマイグレーションファイル作成
$ heroku run rake db:migrate参考
- 投稿日:2019-05-29T20:13:16+09:00
WindowsでWSL、Ubuntu、Rubyをインストール
「Windows Subsystem for Linux上のUbuntu上のRuby」のメモです。やったのは下記。
- Windows 10でWSL(Windows Subsystem for Linux)を有効化してUbuntuインストール
- Ubuntu上でrbenvインストール
- rbenvを使ってRubyインストール
- bundlerを使ってgem(ライブラリ)インストール
ローカルにRuby環境を整えておこうと思ったのだけど、これまでよく使っていたPerlではWindows固有の問題で手間取ることがあったので、「Windows版Ruby」「Windows Subsystem for Linux上のUbuntu上のRuby」の両方を用意することにしました。これは後者の作業メモということになります。
Windows 10上にUbuntuを導入
「【WSL入門】第1回 Windows 10標準Linux環境WSLを始めよう:ITの教室 - @IT」の「まずはWSLとディストリビューションをインストールしよう」の項に従って、以下を行います。
- [コントロールパネル]-[アプリ]-[プログラムと機能]-[Windowsの機能]ダイアログを開き、[Windowsの機能の有効化または無効化]をクリックしてダイアログを開き、そこで「Windows Subsystem for Linux」にチェックを入れる。再起動が要求されるので、再起動を実行すること。
- Microsoft Storeを開き、「Ubuntu」を検索してインストールする
- Ubuntuが起動したら、ユーザー作成のため、ユーザー名とパスワードを入力する。
実はいきなり2.をやろうとして、Ubuntuのインストールが終わらないなと思ったら、「WSLが有効になっていません」という旨の英語メッセージが表示されていました。1.からやり直せば、特に問題はありませんでした。64bitマシンで、Windows Updateも行われている(バージョン1803以降になっている)環境であれば、問題は起こらなそうに思います。
Rubyのインストール
Ubuntuにgitとrbenvを入れる
rbenvを使用してアプリケーションのRubyバージョンを選択し、開発環境と本番環境が一致していることを確認してください。rbenvをBundlerと連携させて、簡単に万全のRubyアップグレードができるようにしてください。(rbenv/README.md先頭部より、意訳)
...ということで、RubyのインストールにはRubyバージョンの管理・切り替えをしてくれるらしいrbenvを使うことにします。rbenvのインストールは、「Basic GitHub Checkout」に従うことにします。
Ubuntuにログインした状態で、まずこれに必要な
git
を下記コマンドでインストールします。sudo apt install git
sudo
を使うときにはパスワードを聞かれることがあるので、その時はUbuntuインストール時に設定したパスワードを入力します。次に、前述の「Basic GitHub Checkout」手順に従って、以下のコマンドでrbenvの最新版を取得します。
git clone https://github.com/rbenv/rbenv.git ~/.rbenvパスを通すため、以下を実行します。前述の手順には各環境用のコマンドが列記されていますが、Ubuntu Desktop用の以下のコマンドを使います。
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc以下を実行します。これはUbuntu on WSL上で
rbenv init
した際の出力に従った作業です。echo 'eval "$(rbenv init -)"' >> ~/.bashrcここまで来たら、一度
exit
してUbuntuを終了し、再度スタートメニューなどから呼び出します。ここまで更新してきた.bashrc
が読み込まれて、パスなどが反映された状態になります。この時点で、rbenvはinstallコマンドを持っていません。これを使えるようにするために、Optionalとなっていruby-buildもインストールします。ruby-buildのInstallation項にあるrbenvのプラグインとしてのインストール方法に従って、以下の二行を実行します。
mkdir -p "$(rbenv root)"/plugins git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-buildUbuntuにRubyを入れる
まずRubyインストール時に必要となるパッケージを入れておきます。実際に、これをせずに後述の
rbenv install
を進めていこうとすると、まずコンパイラ等々がない旨のエラーが出るので、build-essential
パッケージをインストールします。sudo apt install build-essentialまた、この後インストールを進めるとコンパイル時に、SSL関連等のパッケージが必要、以下を実行するようにとの旨のエラーが出るので、これもやっておくことにします。
sudo apt-get install -y libssl-dev libreadline-dev zlib1g-devこれでrbenvによるRubyのインストールが可能になります。以下を実行して、利用できるRubyバージョンを確認します。
rbenv install --list表示された中で、
jruby
等の接頭辞がつかないものでは、2.6.3
と2.7.0-dev
が新しそうでした。Ruby言語の公式サイトを見ても、2.6.3
が最新版のようだったので、これをインストールすることにします。rbenv install 2.6.3このコマンドが成功すれば、インストール完了です。
もし
--list
した時点で最新版のRubyが表示されないようであれば、rbenvが古いのではないかと思います。その時は以下のように.rbenv
ディレクトリに移動してgit pull
することで、rbenv自体を更新すると、表示されるようになるかもしれません。cd ~/.rbenv git pull cd ~通常利用するRubyバージョンを指定する
この時点で
ruby -v
等すると、次のようなメッセージが出ます。rbenv: ruby: command not found The `ruby' command exists in these Ruby versions: 2.6.3rbenv globalの手順に従って以下を実行することで、通常利用するRubyバージョンを指定しておきます。
rbenv global 2.6.3以下を実行して、
ruby
コマンドを実行できること、2.6.3班が動作することを確認します。ruby -v以下のような表示が出ればOKです。
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]以降、明示的に利用バージョンが指定されたディレクトリ以外では、
rbenv global
で指定したバージョンのrubyが呼び出されます。Rubyインストール以降の話
bundlerのインストール
Rubyの様々なライブラリは「GEM」と呼ばれていて、
gem
コマンドでインストールできるようです。しかし前に読んだように、rbenvのドキュメントでは「Bundlerと連携させ」ることを推奨していました。Bundlerがインストールされていることを確認します。以下のコマンドを実行して、インストール済みのgemを確認します。gem list先頭からアルファベット順でインストール済みのgemが表示されます。Ubuntu on Windows上にインストールしたRuby 2.6.3では次のような表示で、
bundler
がインストール済みなのを確認できました。*** LOCAL GEMS *** bigdecimal (default: 1.4.1) bundler (default: 1.17.2) cmath (default: 1.0.0) (以下略)他のRubyバージョンをインストールしていてbundlerがインストールされていなかった場合は、Bundlerの「Getting started」に沿って、以下のコマンドを実行します。
gem install bundlerこれでbundlerを使ってgemをインストールできます。ただしその前に、次の項も確認しておいてください。
Rubyやgemの追加時の作業
rbenvでRubyを管理している場合は、次のタイミングではrbenv rehashをします。
- 別のバージョンのRubyを追加した
- コマンドを提供するgemを追加した
もし前の項でbundlerをインストールした場合、このgemは
bundler
やbundle
などのコマンドを提供するので、上記作業が必要なタイミングです。こうしたタイミングでは、以下を実行します。
rbenv rehashgemのインストール
Bundlerでgemをインストールします。まず、インストール対象を記載するGemfileを作成します。bundle initを実行すると、カレントディレクトリにGemfileが作成されます。
bundle initGemfileには様々な情報を記載できますが、上記手順で作成した場合はデフォルトの
source
やgit_source
が記載されているので、まずは追加したいgemの指定を追記するだけで使えます。例えば下記のコマンドを実行して、nokogiri
を追加します。echo 'gem "nokogiri"' >> GemfileGemfileの更新が済んだら、以下のようにbundle installを実行してgemをインストールします。
bundle install前述のとおり、必要であれば
rbenv rehash
も実行しておきます。インストールを行うと、Gemfile.lockというファイルが生成されています。このファイルには、実際にインストールされたgemがバージョンなどの情報と合わせて記録されています。
参考
Windows 10でのWSLの有効化とUbuntuのインストールについては、以下を参照しました。
Ruby環境の構築では、以下を参照しました。構築実施時には、これらのドキュメントも参照し、本ページの情報が古くないかを確認してください。
この他に、以下のページを参考にしました。これらを参考に試行して、その後前述のリンク先で各ステップが何をしているのか、どれが必須でどれが不要なステップか、記述が分かれるステップはどれが正しいのかなどを確認しながらまとめなおしたのが本内容になります。
- 投稿日:2019-05-29T19:33:58+09:00
Ruby製CLIツールの起動時間を最適化する
以下のようなbinstubを作りましょう。
#!/usr/bin/env ruby require 'bootsnap' gem_name = 'rubocop' exe_name = 'rubocop' cache_dir = File.join(ENV['HOME'], '.cache', 'bootsnap', gem_name, exe_name) Bootsnap.setup(cache_dir: cache_dir, load_path_cache: false, autoload_paths_cache: false) load Gem.bin_path(gem_name, exe_name)
Gem.bin_path
1は2つ引数を取ります。1つ目がgemの名前で2つ目が実行ファイルの名前です。
gem_name
/exe_name
を編集するとrubocop以外の他のCLIツールにもこのbinstubを適用できます。このbinstubなしの場合
% time rubocop -v 0.70.0 rubocop -v 0.76s user 0.20s system 97% cpu 0.983 totalこのbinstubありの場合、初回は多少多めに時間がかかる
% time bin/rubocop -v 0.70.0 bin/rubocop -v 1.04s user 0.57s system 98% cpu 1.640 totalしかし一度実行してしまえばキャッシュが作られてるので早くなる
% time bin/rubocop -v 0.70.0 bin/rubocop -v 0.53s user 0.23s system 79% cpu 0.969 totalCLIツールのソースコードを一切変更せずbinstubつくるだけで起動時間を最適化できます。
やったね ✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌
- 投稿日:2019-05-29T18:57:35+09:00
Ruby ターミナル上で動くヌメロンを作成してみた
ヌメロンとは
簡単にいうと数当てゲーム
今回は、コンピューターが持つランダムな数値列を当てたら勝ちルールについて
- それぞれのプレイヤーが0~9までの数字が書かれたカードを使って3桁または4桁の番号を作成する。ただし、「112」「121」といった同じ数字を2つ以上使用した番号は作れない。
- 先攻のプレイヤーは相手が作成したと思われる番号をコールする。相手はコールされた番号と自分の番号を見比べ、コールされた番号がどの程度合っているかを発表する。
- 数字と位置が合っていた場合は「EAT」
- 数字は合っているが位置は合っていない場合は「BITE」
- 例として相手の番号が 「765」、コールされた番号が「746」であった場合「1 EAT - 1 BITE」となる。
- これを先攻・後攻が繰り返して行い、先に相手の番号を完全に当てきったプレイヤーの勝利となる。
ルールは Numer0n数当てゲーム から抜粋しました。
今回作成したもの
今回はコンピュータのもつ数字を当てていくスタイルを採用しました
一方的に攻めていきますソースコード
numelon.rbclass Numelon #桁数を3桁か4桁で選択できる def select_digits puts "プレイする桁数を選んでください\n3 or 4" @select_num = gets.to_i if @select_num == (3 || 4) return @select_num else puts "桁数が間違っています" select_digits end end #ランダムな数値列の作成 def make_num program_num = [*(0..9)] rand_array = program_num.shuffle @rand_num = rand_array.take(select_digits) end #入力された数値を取得 def enter_num puts "#{@select_num}桁の数字を入力してください" num = gets.chomp enter_array = num.split('').map(&:to_i) #数字の重複を調べる num_dup = enter_array.group_by(&:itself).map{ |key, value| value.count } num_dup.delete(1) #桁数が違う場合、再入力を求める if enter_array.length != @select_num puts "桁数が違います。再入力してください" enter_num #同じ数字が入力されていた場合、再入力を求める elsif num_dup.length != 0 puts "同じ数字が入力されています。再入力してください" enter_num else check_num(enter_array) end end def check_num(enter_array) eat_count = 0 bite_count = 0 enter_array.each_with_index {|num, i| if num == @rand_num[i] eat_count += 1 elsif @rand_num.include?(num) bite_count += 1 end } puts "#{eat_count} eat - #{bite_count} byte" if eat_count == @select_num puts "あなたの勝ちです" exit else enter_num end end end player = Numelon.new player.make_num player.enter_num実際のプレイ
ruby numelon.rb プレイする桁数を選んでください 3 or 4 5 桁数が間違っています プレイする桁数を選んでください 3 or 4 3 3桁の数字を入力してください 12345 桁数が違います。再入力してください 3桁の数字を入力してください 112 同じ数字が入力されています。再入力してください 3桁の数字を入力してください 789 0 eat - 2 byte 3桁の数字を入力してください 198 1 eat - 0 byte 3桁の数字を入力してください 837 0 eat - 3 byte 3桁の数字を入力してください 378 3 eat - 0 byte あなたの勝ちです最後に
rubyの勉強ということで今回はヌメロンを作成
本家のヌメロンは対人戦だから、コンピューターと戦わせようと思ったけど難しかったから一方的に当てるスタイルにしてみた時間ができたらひらがなヌメロンや英単語ヌメロンを作りたい
クラス・メソッドについての理解が浅いので汚いコードになっています
もっと簡潔な記述などあればコメントで教えてください!
- 投稿日:2019-05-29T17:01:41+09:00
Rubyで全探索(深さ優先探索、幅優先探索)を実装してみた
はじめに
アルゴリズム、というか競技プログラミングに関するネタです。
昨日までの自分にとって有益な情報なので、Qiitaに載せておきます。これまで再帰を使った深さ優先探索しか理解できておらず、しかも理解が甘くて使おうとしても毎度ワタワタする始末。これではいかんと思い、以下3つのパターンについて極力シンプルな形で実装してみました。
- 深さ優先探索(DFS, Depth-First Search)
- 再帰による実装
- スタックによる実装
- 幅優先探索(BFS, Breadth-First Search)
- キューによる実装
すべてRubyでの実装です。Ruby 2.3.3での動作を確認しています。
お題
アルファベット
A, B, C
からなる、3文字以下の文字列の組み合わせをすべて取得する。このお題を、全探索で解きます。この手の全探索だと"迷路を解く"問題が定番ですが、シンプルさを重視した結果こんなお題です。
深さ優先探索
再帰による実装
文字数が3文字になるまで、現在の文字列に追加の一文字を足して同じメソッドに突っ込む、というスタイルです。停止条件における
return
がキモ。個人的にはこれが一番腑に落ちるスタイル(元々これしか使えていなかったというのもあるけど)。ただ、停止条件やその他の処理が複雑になると途端にわけが分からなくなるのもこのパターンかなぁ、という気がしています。
$debug = true def recursive_dfs(patterns, string="") # 文字列を配列に格納する patterns << string unless string.empty? # 停止条件: 文字列長が3文字になること if string.length == 3 patterns << "<leaf>" if $debug return end # 分岐処理 recursive_dfs(patterns, string+"A") recursive_dfs(patterns, string+"B") recursive_dfs(patterns, string+"C") end patterns = [] # 結果を格納する配列を用意しておく recursive_dfs(patterns) p patterns # 組み合わせの表示 # => ["A", "AA", "AAA", "<leaf>", "AAB", "<leaf>", "AAC", "<leaf>", "AB", "ABA", "<leaf>", "ABB", "<leaf>", "ABC", "<leaf>", "AC", "ACA", "<leaf>", "ACB", "<leaf>", "ACC", "<leaf>", "B", "BA", "BAA", "<leaf>", "BAB", "<leaf>", "BAC", "<leaf>", "BB", "BBA", "<leaf>", "BBB", "<leaf>", "BBC", "<leaf>", "BC", "BCA", "<leaf>", "BCB", "<leaf>", "BCC", "<leaf>", "C", "CA", "CAA", "<leaf>", "CAB", "<leaf>", "CAC", "<leaf>", "CB", "CBA", "<leaf>", "CBB", "<leaf>", "CBC", "<leaf>", "CC", "CCA", "<leaf>", "CCB", "<leaf>", "CCC", "<leaf>"]ちなみに、停止条件(文字列長が3文字)のタイミングが明確になるように、組み合わせ結果に
<leaf>
という文字列を追加しています。先頭行を$debug = false
とすればこの処理は行われません(他パターンのコードでも同様)。再帰のネストが深くなりやすい点については注意した方がいいかもしれません。
スタックによる実装
同じく深さ優先探索ですが、再帰ではなくスタックを用います。現在の文字列に一文字追加したものをスタックに積んでいきループで処理する、という構造にすることで再帰を回避しています。
そのため、再帰による実装と違ってネストが深くならないところはメリットですね。
$debug = true def iterative_dfs(patterns) # スタックとなる配列を用意し、初期状態である空文字列を格納する stack = [""] until stack.empty? # スタックから1つ取り出す(末尾から取り出すのでpopを使う) string = stack.pop # 文字列を配列に格納する patterns << string unless string.empty? # 停止条件: 文字列長が3文字になること if string.length == 3 patterns << "<leaf>" if $debug else # 分岐処理 stack << string+"C" stack << string+"B" stack << string+"A" end end end patterns = [] # 結果を格納する配列を用意しておく iterative_dfs(patterns) p patterns # 組み合わせの表示 # => ["A", "AA", "AAA", "<leaf>", "AAB", "<leaf>", "AAC", "<leaf>", "AB", "ABA", "<leaf>", "ABB", "<leaf>", "ABC", "<leaf>", "AC", "ACA", "<leaf>", "ACB", "<leaf>", "ACC", "<leaf>", "B", "BA", "BAA", "<leaf>", "BAB", "<leaf>", "BAC", "<leaf>", "BB", "BBA", "<leaf>", "BBB", "<leaf>", "BBC", "<leaf>", "BC", "BCA", "<leaf>", "BCB", "<leaf>", "BCC", "<leaf>", "C", "CA", "CAA", "<leaf>", "CAB", "<leaf>", "CAC", "<leaf>", "CB", "CBA", "<leaf>", "CBB", "<leaf>", "CBC", "<leaf>", "CC", "CCA", "<leaf>", "CCB", "<leaf>", "CCC", "<leaf>"]以下、処理の流れを追ってみます。
スタックには初期値である空の文字列
""
だけが格納されている状態です。
空文字列とは言ってもスタック自体が空というわけではないので、untilループがスタートします。
スタックから""
を取り出しますが、空文字列なのでpatterns
には格納しません。
スタックはこれで空っぽになりました。
""
の文字列長は0なので、一文字を追加した"C", "B", "A"をスタックに積みます。
スタックの中身は"C", "B", "A"です。スタックは空ではないため、untilループが続行します。
末尾から"A"
を取り出してpatterns
に格納します。
スタックには"C", "B"
が残ります。
"A"
の文字列長は1なので、一文字を追加した"AC", "AB", "AA"
をスタックに積みます。
スタックの中身は"C", "B", "AC", "AB", "AA"
です。スタックは空ではないため、untilループが続行します。
末尾から"AA"
を取り出してpatterns
に格納します。
スタックには"C", "B", "AC", "AB"
が残ります。
"AA"
の文字列長は2なので、一文字を追加した"AAC", "AAB", "AAA"
をスタックに積みます。
スタックの中身は"C", "B", "AC", "AB", "AAC", "AAB", "AAA"
です。スタックは空ではないため、untilループが続行します。
末尾から"AAA"
を取り出してpatterns
に格納します。
スタックには"C", "B", "AC", "AB", "AAC", "AAB"
が残ります。
"AAA"
の文字列長は3なので、停止条件に合致します。
スタックへの追加は行いません。
スタックの中身は"C", "B", "AC", "AB", "AAC", "AAB"
です。スタックは空ではないため、untilループが続行します。
末尾から"AAB"
を取り出してpatterns
に格納します。
スタックには"C", "B", "AC", "AB", "AAC"
が残ります。
"AAB"
の文字列長は3なので、停止条件に合致します。
スタックへの追加は行いません。
スタックの中身は"C", "B", "AC", "AB", "AAC"
です。以下略。最後にはスタックに残った
"CCC"
を取り出して終わります。分岐処理での文字追加をアルファベットの逆順にしているのは、再帰による実装と結果の出力を同じにしたかったためという表示上の都合です。
"A", "B", "C"
の順番だとどうなるか分からない場合は実際にそう書き換えて実行してみよう。幅優先探索
キューによる実装
今度は幅優先探索です。とは言っても、スタックによる深さ優先探索とほとんど同じで、現在の文字列に一文字追加したものをキューに積んでいく、という処理を行っています。実質的に違っているのはスタックかキューかという部分だけ、つまり積んだものを末尾から取り出すか先頭から取り出すかの違いでしかありません。
$debug = true def iterative_bfs(patterns) # キューとなる配列を用意し、初期状態である空文字列を格納する queue = [""] until queue.empty? # キューから1つ取り出す(先頭から取り出すのでshiftを使う) string = queue.shift # 文字列を配列に格納する patterns << string unless string.empty? # 停止条件: 文字列長が3文字になること if string.length == 3 patterns << "<leaf>" if $debug else # 分岐処理 queue << string+"A" queue << string+"B" queue << string+"C" end end end patterns = [] # 結果を格納する配列を用意しておく iterative_bfs(patterns) p patterns # 組み合わせの表示 # => ["A", "B", "C", "AA", "AB", "AC", "BA", "BB", "BC", "CA", "CB", "CC", "AAA", "<leaf>", "AAB", "<leaf>", "AAC", "<leaf>", "ABA", "<leaf>", "ABB", "<leaf>", "ABC", "<leaf>", "ACA", "<leaf>", "ACB", "<leaf>", "ACC", "<leaf>", "BAA", "<leaf>", "BAB", "<leaf>", "BAC", "<leaf>", "BBA", "<leaf>", "BBB", "<leaf>", "BBC", "<leaf>", "BCA", "<leaf>", "BCB", "<leaf>", "BCC", "<leaf>", "CAA", "<leaf>", "CAB", "<leaf>", "CAC", "<leaf>", "CBA", "<leaf>", "CBB", "<leaf>", "CBC", "<leaf>", "CCA", "<leaf>", "CCB", "<leaf>", "CCC", "<leaf>"]深さ優先探索の場合の出力結果と異なり、1文字の結果、2文字の結果、3文字の結果というように文字列長ごとに結果が並びます。これが都合良い、というケースもあるでしょうね。まぁ別のやり方使ってからソートすればいいという気もしますが。
こちらも、処理の流れを追っておきます。
キューには初期値である空の文字列
""
だけが格納されている状態です。
空文字列とは言ってもキュー自体が空というわけではないので、untilループがスタートします。
キューから""
を取り出しますが、空文字列なのでpatterns
には格納しません。
キューはこれで空っぽになりました。
""
の文字列長は0なので、一文字を追加した"A", "B", "C"をキューに積みます。
キューの中身は"A", "B", "C"です。キューは空ではないため、untilループが続行します。
先頭から"A"
を取り出してpatterns
に格納します。
キューには"B", "C"
が残ります。
"A"
の文字列長は1なので、一文字を追加した"AA", "AB", "AC"
をキューに積みます。
キューの中身は"B", "C", "AA", "AB", "AC"
です。キューは空ではないため、untilループが続行します。
先頭から"B"
を取り出してpatterns
に格納します。
キューには"C", "AA", "AB", "AC"
が残ります。
"B"
の文字列長は1なので、一文字を追加した"BA", "BB", "BC"
をキューに積みます。
キューの中身は"C", "AA", "AB", "AC", "BA", "BB", "BC"
です。キューは空ではないため、untilループが続行します。
先頭から"C"
を取り出してpatterns
に格納します。
キューには"AA", "AB", "AC", "BA", "BB", "BC"
が残ります。
"C"
の文字列長は1なので、一文字を追加した"CA", "CB", "CC"
をキューに積みます。
キューの中身は"AA", "AB", "AC", "BA", "BB", "BC", "CA", "CB", "CC"
です。キューは空ではないため、untilループが続行します。
先頭から"AA"
を取り出してpatterns
に格納します。
キューには"AB", "AC", "BA", "BB", "BC", "CA", "CB", "CC"
が残ります。
"AA"
の文字列長は2なので、一文字を追加した"AAA", "AAB", "AAC"
をキューに積みます。
キューの中身は"AB", "AC", "BA", "BB", "BC", "CA", "CB", "CC", "AAA", "AAB", "AAC"
です。中略。
キューは空ではないため、untilループが続行します。
先頭から"AAA"
を取り出してpatterns
に格納します。
キューには"AAB", "AAC", "ABA", "ABB", "ABC", ..., "CCB", "CCC"
が残ります。
"AAA"
の文字列長は3なので、停止条件に合致します。
キューへの追加は行いません。
キューの中身は"AAB", "AAC", "ABA", "ABB", "ABC", ..., "CCB", "CCC"
です。以下略。最後にはキューに残った
"CCC"
を取り出して終わります。注意点
スタックによる深さ優先探索とキューによる幅優先探索は実装コードがほぼ同じですが、キューによる幅優先探索には、『探索するパターンが増えると、キューが巨大化する傾向にある』という欠点があります。
お題の通りの条件では、スタックの最大長が7なのに対してキューの最大長は27です。これくらいだとそれほど大差はありません。
ですが、たとえば文字の種類が"A"から"E"までの5種類で文字列長が8文字以下、という条件の場合だと、スタックの最大長が33なのに対してキューの最大長は390,625となります。
おわりに
シンプル化するとどれもかなりカンタンなコードになりますねぇ。
これらの中だとスタックによる深さ優先探索が一番混乱しにくいコードを書けそうだと感じました。ただ、今の所再帰による実装の方に慣れてしまっているので、分岐処理を"スタックに収められる形で書く"ことに難儀しそうな気も……。文中の間違いの指摘や改善のためのアドバイスなど、大歓迎です。
- 投稿日:2019-05-29T16:37:29+09:00
Ruby CSV取り込み ヘッダーの上にいらない行がある場合の処理
はじめに
CSV取り込み処理を作ることって結構ありますよね?
たまに1行目がヘッダーではないCSVデータってありますよね?
あれ何なんでしょうね?
何でそんなことをするのかわからないんですけど、取り込まないといけないですよね?(使命感)どんなデータ?
実装
1、2行目をスキップして行を配列として取得するだけなら単純なんですが、
CSV.foreachと同じようにrow['ヘッダー名']でデータを取得できるようにするには一工夫が必要です。# CSVをオープン csv = CSV.open( path, undef: :replace, replace: '?' ) # ヘッダの上に邪魔な行が2行あるので除去 csv.readline csv.readline # ヘッダを退避 header = csv.readline csv.each do |row| # CSV.foreachと同じようなデータの取り方にパース row = CSV.parse_line(row.join(','), headers: header) endおわりに
邪魔な行があったりなかったりする場合の処理はよしなにやってください。
ブログで見たい方はこちら
Ruby CSV取り込み ヘッダーの上にいらない行がある場合の処理
- 投稿日:2019-05-29T16:31:55+09:00
【Rails】ネストされたcontroller宛てのform_withの書き方
namespaceでnestされたcontroller(Admin::Userなど)のアクション宛てformを
form_with
で記述したい場合の雛形になります。書き方
◆ new → create
new.html.erbのviewから
Admin::UsersController
のcreate
アクションへ送りたい場合、htmlレベルでは<form action="/admin/users" method="post" > <input type="text" name="user[name]"> </form>こうなっていてほしいが、form_withではどう記述すればよいか?
① model: を使う方法
model:
で@userを指定し、url:
でpathを指定します。new.html.erb<%= form_with model: @user, url: admin_users_path, local: true do |f| %> <%= f.text_field :name %> <% end %>② model: を使わない方法
scope:
で:user
を指定し、url:
でpathを指定します。
scope: :user
によってパラメーターのuser[]
を生成するようです。new.html.erb<%= form_with scope: :user, url: admin_users_path, local: true do |f| %> <%= f.text_field :name %> <% end %>(ちなみに、
scope: :user
をなくすと)new.html.erb<%= form_with url: admin_users_path, local: true do |f| %> <%= f.text_field :name %> <% end %>↓htmlレベルでは
<form action="/admin/users" method="post" > <input type="text" name="name"> </form>
user[name]
ではなく、name
になっています。◆ edit → update
同様に、edit.html.erbから
Admin::UsersController
のupdate
アクションへ送りたい場合、htmlレベルでは<form action="/admin/users/3" method="post"><input type="hidden" name="_method" value="patch" /> <input type="text" name="user[name]"> </form>こんな感じになっていてほしいが、form_withではどう記述すればよいか?
① model: を使う方法
model:
で@userを指定し、url:
でpathを指定します。edit.html.erb<%= form_with model: @user, url: admin_user_path, local: true do |f| %> <%= f.text_field :name %> <% end %>② model: を使わない方法
scope:
で:user
を指定し、url:
でpath、method:
で:patch
を指定します。edit.html.erb<%= form_with scope: :user, url: admin_user_path, method: :patch, local: true do |f| %> <%= f.text_field :name %> <% end %>(ちなみに、
method: :patch
をなくすと)edit.html.erb<%= form_with scope: :user, url: admin_user_path, local: true do |f| %> <%= f.text_field :name %> <% end %>↓htmlレベルでは
<form action="/admin/users/3" method="post"> <input type="text" name="user[name]"> </form>
<input type="hidden" name="_method" value="patch" />
が無くなりました。
これにより「htmlメソッドはpatchという体裁で送っているよ」というメッセージが消えたので、railsサーバーはPOSTが届いたと認識します。(詳しくはこちら)
methodがPOST、pathがadmin_user_pathの組み合わせは存在しないので、Routing Errorになるはずです。結論
form_withでは
model:
を使えば空気を読んで自動翻訳くれるのでかなり楽。
しかし、nestされている状態とpathまでは空気読みきれないので、mode:
とurl:
のセットを使えばOK.
- 投稿日:2019-05-29T13:17:13+09:00
RailsのService層のクラスをauto_loadpathに含める
環境
- Ruby 2.4.2
- Rails 5.1.4
やりたいこと
app - services/ - neko.rb - - utils/ - - - hoge.rb - - users/ - - - test.rbみたいな
serivces
ディレクトリ構成があるとして、全てのディレクトリ内のファイルをオートロードパスに含めたいが、デフォルトでは出来ないので、その方法について記載します。デフォルトでは、
services
ディレクトリの直下のみがオートロードパスの対象となっています。services.rbを編集
config/initializers
ディレクトリ内にserices.rb
を作成します(自分の環境にはありませんでした)。
services.rb
を以下のようにします。
rb
Dir.glob("app/services/**/*.rb") do |file|
require_dependency Rails.root.join(file)
end
これで全てのファイルが対象となっているかと思います。
- 投稿日:2019-05-29T12:55:38+09:00
【Haml】条件(if)に応じてClassを変更させる方法
例えば、Eventというモデルの編集ページにおいて、編集不可の要素に対して付与する
is-disabled
というクラスを用意したとします。.event-panel.is-disabledEventがeditable?でないときのみ
is-disabled
を付与する場合は以下のように書きます。- event = @event .event-panel{class:('is-disabled' unless event.editable?)}
- 投稿日:2019-05-29T12:39:05+09:00
Rails4でhealth_checkをミドルウェア化してヘルスチェックを行う
使用するGemは↓
https://github.com/ianheggie/health_check/tree/v2.8.0
Rails4なのでREADMEにあるとおりv2.8.0を使う。install
Gemfileの記述
gem 'health_check', "~> 2.8"
config
config/initializers/health_check.rb
に色々書いていく。
GithubのREADMEにサンプルもあるので基本はそれを編集して作る。ルーティング
↓のように書いておくと↓↓で指定したURIからアクセスできるようになる。
ちなみにpassengerなどを使ってRailsを起動していると再起動が必要になるので注意。config/routes.rbhealth_check_routes
config/initializers/health_check.rbconfig.uri = 'health_check'ヘルスチェック方法
http://portal-dev.lierco.com/health_check.json
でJsonも取れる。↓チェック項目もパラメータで指定可能。
未指定だとstandard
が使用され、DBやマイグレーションなどがチェックされる。
full
orall
も可能でstandardよりも項目数が多い。
何がstandardなのかといった項目も変更可能で、変更方法は↓のミドルウェア化の項目を参考に。http://portal-dev.lierco.com/database http://portal-dev.lierco.com/standard http://portal-dev.lierco.com/fullミドルウェア化
DBへ接続できないときなどはRails自体が死ぬのでヘルスチェックすらできなくなる。
そうなるのを回避するためにミドルウェア化する。config/application.rbconfig.middleware.insert_after "Rails::Rack::Logger", HealthCheck::MiddlewareHealthcheckこのとき
config.middleware_checks
をミドルウェアでチェックする項目を指定することができる。
デフォルトだとDB接続は入ってないので指定しないとミドルウェアでDB接続をチェックしてくれなくなる。ここで指定したものとURLを比較してミドルウェアで行うか判断しているみたいなので
standard
やfull
などを使う場合は注意が必要。
/health_check/
(/health_check/standard
と同じ)やhealth_check/all
にアクセスしてDBのヘルスチェックをミドルウェアでしてもらうには例えば↓のようにする。
逆にfull
だけ指定してall
は指定しないとかすると挙動を変えられそう(未検証)。config/initializers/health_check.rbconfig.middleware_checks = ['middleware', 'migrations', 'standard', 'full', 'all'] config.standard_checks = ['database', 'migrations'] config.full_checks = ['database', 'migrations', 'custom', 'email', 'cache', 'redis', 'resque-redis', 'sidekiq-redis', 's3']ステータスコードを変更
ヘルスチェックの結果がエラーだった場合に返るステータスコードを変更することができる。
テキストで受け取る際とオブジェクト(XMLやJson)で受け取る場合で別に指定できる。config/initializers/health_check.rbconfig.http_status_for_error_text = 503 config.http_status_for_error_object = 503動作確認
ミドルウェア化によりDBに接続できない場合でもヘルスチェックができることを確認する。
- まずは通常通りRailsを起動して
/health_check
にアクセスする
- →
success
の文字が確認できるsudo service mysqld stop
としてDBを落として/health_check
にアクセスする
- →
health_check failed: closed MySQL connection
になる- curlに
-i
オプションを付けて確認するとステータスコードが503
であることも確認できる- DBが落ちたまま
/health_check
以外のページにアクセスする
- → Railsがエラーとスタックトレースを出す
- 投稿日:2019-05-29T12:22:41+09:00
Spacemacsはいいぞ
Spacemacs is なに?
- Emacsディストリビューション。Spacemacsよりも軽量なものにDoom Emacsなんかがあったりする。
- Vimの秀逸な入力インターフェイスにEmacsの強力な拡張性を併せ持つそれぞれのいいとこ取りのエディタ。
メリット
- スペースキーを起点としたコマンドが秀逸。スペースキーを押した時点で エディタの下に次のキーの候補と何のコマンドかを表示してくれて親切。
- デフォルトで多くレイヤーが用意されていて、
.rb
や.py
などの拡張子を初めて開く場合、最初にawesome-emacsに乗っているようなモジュールを入れるか聞いてくれるので言われるがままに入れとけばまず困らない。- 補完が結構効く。仕事でRubyを書いていてRubyMineでは設定が悪いのか全然補完してくれなかったものがSpacemacsでは補完してくれるようになってRubyMineやめた。Robeすごい。
- Emacsの豊富な既存のモジュールをほぼそのまま使える。
- themes-megapackレイヤーを入れれば有り余るほどテーマが入るので、私みたいに定期的にテーマを変えたい人にはありがたい。テーマの変更は設定ファイルからできる他に
Space T s
で簡単に変更できる。デメリット
- 情報が少ないのでSpacemacs固有の問題にぶち当たったら解決するのが結構大変らしい。
- 立ち上げてしまえばサクサクだが、やはり素のEmacsやVimやVSCodeと比べて起動が遅い。(それでもRubyMineやその他IDEよりはずっと速い)
導入手順
私はmacOSを使っているためmacOSでの導入手順を書いておきます。その他OSの方はGithubのREADMEを参照してください。
また、すでにemacsを使っている方は
~/.emacs.d
のバックアップを忘れずにしてください。$ brew tap d12frosted/emacs-plus $ brew install emacs-plus $ brew linkapps emacs-plus $ git clone https://github.com/syl20bnr/spacemacs ~/.emacs.dよく使うコマンド
.rb
ファイルを開いた状態でSpace m '
: Robe(コードナビゲーション、ドキュメント検索、オートコンプリート)の起動Space p f
: プロジェクト内のファイル検索Space /
: プロジェクト内のテキスト検索Space f f
: ファイル検索Space T T
: エディタの透過調整Space g b
: git blameSpace T s
: エディタのテーマ変更Space w -
: エディタの水平分割Space w v
: エディタの垂直分割Space w w
: 次のウィンドウに移動Space {0~9}
: 指定した数字のウィンドウに移動Space p t
: プロジェクトツリー(Treemacs)
- 投稿日:2019-05-29T11:54:44+09:00
Rails herokuデプロイ時のエラーについて
rails初学者です。
herokuにアプリをデプロイしたところ、特定のページでのみエラーとなります。
試行錯誤を繰り返したのですが、自力での解決が難しいため質問させてください。下記、「heroku logs -t」でのエラー文です。
PG::DatatypeMismatch: ERROR: argument of WHERE must be type boolean, not type integer LINE 1: SELECT "users".* FROM "users" WHERE (1) LIMIT $1 : SELECT "users".* FROM "users" WHERE (1) LIMIT $1): app/controllers/posts_controller.rb:89:in `follows'上記の文面から、postsコントローラーのfollowsアクションで、「WHERE句はinteger型ではなくboolean型ですよ。」との内容だと思うのですが...
修正が必要と思われる箇所はこちらです↓posts_controller.rbdef follows @user = User.find_by(params[:id]) #ここが89行目 @follows = Follow.where(follower_id: @user.id) @posts = [] if @follows.present? @follows.each do |follow| posts = Post.where(user_id: follow.followed_id).order(updated_at: :desc) @posts.concat(posts) end @posts.sort_by!{|post| post.created_at}.reverse! if @posts.nil? flash[:notice]="まだ投稿がありません…" redirect_to("/posts/index") end else flash[:notice]="誰かをフォローしてみましょう!" redirect_to("/posts/index") end説明に不足・見当違いなどございましたらご指摘いただければ幸いです。
ご回答のほど、よろしくお願い致します!
- 投稿日:2019-05-29T11:23:23+09:00
「3ステップでしっかり学ぶRuby入門」 第1~5章まとめ
はじめに
Rubyの勉強を始めて約2週間程度の超初心者です。教えていただいた本「3ステップでしっかり学ぶRuby入門」の第5章までの内容をまとめてみました。拙い文章ですみません。
第1章 プログラミング言語Rubyとは
Rubyとは
Rubyはオブジェクト指向スクリプト言語です。
オブジェクト指向とは、プログラムを「オブジェクト(もの)同士の相互作用」とみなす考え方です。
スクリプト言語とは、プログラムをテキストファイルのままで実行できる言語のことです。Rubyプログラムを実行させる方法
大きく分けて3つあります。
①rubyコマンド-eオプションを指定してプログラムを直接書き、コマンドライン上から実行する方法
②irbで対話的に実行する方法
③Rubyプログラムが記述されたテキストファイルを指定して実行する方法
第2章
変数
変数とはデータにつけるラベルのようなものです。
「変数名 = データ」で表され、右辺のデータが左辺の変数名に代入されます。上記は 1 と出力されます。
また、変数は順次処理によって、出力結果が上書きされていきます。
上記は変数aに3が再代入されているため、3と出力されます。
数値の演算
+、 -、 *、 / を使って四則演算することができます。
足し算
引き算
掛け算
割り算
第3章
配列
配列とは複数のデータを1つにまとめたもので、[要素1, 要素2, 要素3]と表されます。
配列を使うことで複数のデータのまとまりを1つの変数で表現することができます。
上記の出力結果は、[apple, banana, cherry]となります。配列でデータをまとめ、まとめたデータは添字で取り出すことができます。
配列[添字]で表現してみましょう。上記の出力結果は、'cherry'となります。
ハッシュ
ハッシュとは複数のデータをまとめて扱うものです。
配列との最大の違いは、添字に数値ではなく文字列を利用できることです。ハッシュは{ 'キー1' => 値1, 'キー2' => 値2, ...}のようにキーと値のペアを記述して定義します。
第4章
if
ifで、条件式が正しい場合に特定の処理を実行できます。
case
case・・・複数の条件に対応する処理を実行するときに使います。
caseの他にwhen,else,endを使って記述します。
表記方法
第5章
times
timesメソッドを使うと、同じ処理を繰り返すことができます。
↓
変数は0から数値-1まで繰り返しの度に増えます。
each
eachメソッドは、それぞれの要素に対して繰り返し処理をすることができます。
timesメソッドとの違いは、繰り返しの回数を意識することなく繰り返し処理が可能なところです。while
while式を使うと、条件に応じて処理を繰り返すことができます。
whileの性質上、繰り返し処理の結果、ずっと条件式がtrueにならない場合は処理が終わらなくなります。↓
まとめ
より理解を深めて、簡潔にわかりやすく書けるように練習していきます。次回は、メソッド・クラスを中心にまとめたいと思います。
- 投稿日:2019-05-29T11:01:11+09:00
みなさんが必ず使う検索機能について・・・
みなさんこんにちは☀️
検索・・・
それは誰でもしたことあるやつです。インターネットで調べる時、メルカリで欲しいものを検索したい時など。
現代ではスマートフォンによっていつでもどこでも検索することができます。
僕がプログラミング学習しているときでも、
検索を常にしています。
今日はその検索機能をRubyではどう書くのかみていきたいと思います。検索機能
RubyではLIKE旬を利用します。
LIKE旬とは、曖昧な文字列を検索することができることです。
これは、Whereメソッドと一緒に使います。実行サンプルを置いておきます。
aから始まるタイトル
where(‘title LIKE(?)’, “a%”)bで終わるタイトル
where(‘title LIKE(?)’, “%b”)cが含まれるタイトル
where(‘title LIKE(?)’, “%c%”)dで始まる2文字のタイトル
where(‘title LIKE(?)’, “d_”)eで終わる2文字のタイトル
where(‘title LIKE(?)’, “_e”)
- 投稿日:2019-05-29T10:45:28+09:00
Rails6 のちょい足しな新機能を試す26(MySQL utf8mb4 編)
はじめに
Rails 6 に追加されそうな新機能を試す第26段。 今回は、
MySQL の utf8mb4
編です。
Rails 6.0.0 では、 rails new -d mysql を実行(データベースにMySQLを指定)したときに encoding が utf8mb4 になります。Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は
gem install rails --prerelease
でインストールできます。$ rails --version Rails 6.0.0.rc1Rails プロジェクトを作る
-d mysql
を指定します。$ rails new rails6_0_0rc1 -d mysql $ cd rails6_0_0rc1database.yml を確認する
yda database.yml を確認すると、 encoding が utf8mb4 になっていることがわかります。
config/database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 ...User の CRUD を作る
utf8mb4 を試すために、実際に User の CRUD を作ります。
$ bin/rails g scaffold User name
データベースの migration
$ bin/rails db:create db:migrate
rails server
を実行してユーザーを登録する
bin/rails s
を実行して、 http://localhost:3000/users/new にアクセスして User を登録します。
このとき name に?と?
を入力します。$ bin/rails s
Rails 6.0.0rc1 では無事に登録できました。
Rails 5.2.3 ではエラーになります。
蛇足ですが
name
に絵文字はないだろうとか思ったりしましたが、そこはスルーで。
rails g scaffold Book title
とかにした方がまだ良かったかもと思わないでもないです...。スクリーンショットの絵文字とこの記事の絵文字の見え方が違うのは、多分 OS の(フォント?)の違いです。
スクリーンショットは Ubuntu 16.04 の Chrome で取ったものです。試したソース
試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try026_mysql_utf8mb4参考情報
- 投稿日:2019-05-29T09:31:46+09:00
[16??日目]Ruby環境構築 等々
少し更新をサボった。
cmderをコンソールとして使う。
sinatraを触るために https://qiita.com/k-ta-yamada/items/9e35c5f8b31862267e01
このサイトを始めたところ。ローカルアドレスから抜けられないのとルーティングとか追加のところで詰んだ。また参考書”たのしいRuby 第6版”を読み始めた。とりあえず1から写経。
並行して"大学4年間のデータサイエンスが10時間でざっと学べる"を読んでいる。6h位???
- 投稿日:2019-05-29T01:48:11+09:00
color_fieldすご
Articleモデルカラムに色を登録するカラム
color_1
とcolor_2
を追加するcolor_field
jqueryのカラーピッカーとかを使って色を登録しようかなんて思っていたところRailsが提供している
color_field
というものを見つけた。これで色を選択できる!こんなかんじ↓
_form.html.erb<tr> <th scope="row">吹き出し1</th> <td><%= f.text_field :balloon_1, size: "90x10" %> <%= f.color_field :color_1 %> </td> </tr> <tr> <th scope="row">吹き出し2</th> <td><%= f.text_field :balloon_2, size: "90x10" %> <%= f.color_field :color_2 %> </td> </tr>色の表示
<div class = "balloon1" style="background-color:<%= @article_.color_1 %>"><div class = "balloon1" style="background-color:#b8e1f5">
- 投稿日:2019-05-29T01:48:11+09:00
color_fieldとインラインでの擬似要素の表示について
吹き出しの背景色をその時の気分で色を決めたい!
変更前がこちら
やること
Articleモデルに色を登録するカラム
color_1
とcolor_2
を追加するcolor_field
jqueryのカラーピッカーとかを使って色を登録しようかなんて思っていたところRailsが提供している
color_field
というものを見つけた。これで色を選択できる!こんなかんじ↓
_form.html.erb<tr> <th scope="row">吹き出し1</th> <td><%= f.text_field :balloon_1, size: "90x10" %> <%= f.color_field :color_1 %> </td> </tr> <tr> <th scope="row">吹き出し2</th> <td><%= f.text_field :balloon_2, size: "90x10" %> <%= f.color_field :color_2 %> </td> </tr>色の表示
<div class = "balloon1" style="background-color:<%= @article_.color_1 %>"><div class = "balloon1" style="background-color:#b8e1f5">これだとまだ吹き出しの3角の部分の色がstyle.cssで指定したままの色なので未完成です。
この吹き出しのCSSはこのようなコードでできています。style.css.detail .article .balloon1{ position: relative; padding: 20px; border-radius: 10px; background-color: #ffb1cd99; width: 75%; height: 25px; } /* 三角アイコン */ .balloon1::before{ content: ''; position: absolute; display: block; width: 0; height: 0; left: -15px; top: 17px; border-right: 15px solid #ffb1cd99; border-top: 15px solid transparent; border-bottom: 15px solid transparent; }::beforeってのが擬似要素ってやつでこれのスタイル指定を別で行わなきゃいけないようです。
擬似要素ってなあに?って方はこちら↓
この方の記事がわかりやすかったです!
いろんな仕様の変更とかで::
と:
が混じってわかりにくい記事も多いです
参考:擬似要素と擬似クラス<style>.balloon1::before { border-right: 15px solid <%= @article_.color_1 %>; }</style> <div class = "balloon1" style="background-color:<%= @article_.color_1 %>"><%= @article_.balloon_1 %></div>できた!
- 投稿日:2019-05-29T00:05:00+09:00
rubyでいいかんじにDIするhabuの紹介
TL;DR
rubyでいい感じにDIするgemを作りました。使い方を紹介します。いいなと思ったらStarください。
https://github.com/hanachin/habuユーザーがある。
user.rbUser = Struct.new(:name)新しくユーザーを作るサービスがある。これは
user_repository
に依存している。コンストラクタ(#initialize
)を@Inject
でアノテーションする。new_user_service.rbclass NewUserService @Inject def initialize(user_repository) @user_repository = user_repository end def call(*params) @user_repository.new(*params) end endまずはじめに先頭で
require "habu/setup"
する。必要なソースをrequire
する。app.rbrequire 'habu/setup' require_relative 'user' require_relative 'new_user_service'DIコンテナを作成する。
app.rb# Create a new container container = Habu::Container.new
Habu::Container#[]
を呼び出しuser_repository
のつくりかたをブロックで定義する。app.rb# Register user_repository service by passing the block as service factory container[:user_repository] { User }
Habu::Container#new
にUserService
クラスを渡すとuser_repository
をコンストラクタ注入する。いい感じにうごく。app.rb# Call Habu::Container#new to get instance new_user = container.new(NewUserService).call("hanachin") # => #<struct User name="hanachin">
Habu::Container#[]
に渡されたブロックは引数としてHabu::Container
自身をうけとる。
Habu::Container#[]
をブロックなしで呼び出すと定義時に渡されたブロックを呼び出した結果を返す。これにより
new_user
の定義時にuser_repository
を参照できる。app.rb# Factory block take a container as argument container[:new_user] do |c| # You can get the service object by calling Container#[](service_name) NewUserService.new(c[:user_repository]) endブロックなしでよびだす、いい感じに動く。
app.rbnew_user = container[:new_user].call("hanachin") # => #<struct User name="hanachin">DIコンテナを
Habu::Container#to_refinements
でRefinements化してusing
する。
@Inject
したコンストラクタがオーバーライドされているので単に.new
するだけで依存性が注入された状態で呼び出せる。
Habu::Container#new
経由でインスタンス化する必要はない。# Using container as refinements for shorthand for container.new using container.to_refinements new_user = NewUserService.new.call("hanachin") # => #<struct User name="hanachin">動機
「Ruby on Railsの正体と向き合い方」
https://speakerdeck.com/yasaichi/what-is-ruby-on-rails-and-how-to-deal-with-it?slide=59
https://youtu.be/ecpq0U4zkWE?t=1648密結合→疎結合に移行できると良いのではみたいな文脈でLaravelのDIコンテナに言及があった。
あるとべんりそうだしつくるか〜。DIとは
他でいい感じの記事よんでください。
http://dwango.github.io/scala_text_previews/forkme/advanced-trait-di.html#%E4%BE%9D%E5%AD%98%E6%80%A7%E3%81%AE%E6%B3%A8%E5%85%A5%E3%81%A8%E3%81%AF%EF%BC%9F†闇の動機†
Javaの引数がないアノテーションはrubyだとインスタンス変数なのでSyntax OK
inject_annotation.rbclass C @Inject def initialize(c) @c = c end end% ruby -c inject_annotation.rb Syntax OKインスタンス変数による注釈が実装できるとPythonのデコレータの実装もできる。
Raubyをキメると気持ちいい
@Inject
でRubyキメたい。
†注釈術士†になりたい。rubyで
@Inject
の位置を取得するTracePoint
まずみなさんが試すのがTracePointだと思います。
get_inject_location_by_trace_point.rbTracePoint.trace(:line) do |tp| raise if File.read(tp.path).lines[tp.lineno-1].match?(/@Inject/) end class C @Inject def initialize(c) @c = c end end残念ながら取れません。
% ruby get_inject_location_by_trace_point.rb %InstructionSequence
TracePointを使っても実行時に取れないということは? InstructionSequenceを覗いてみましょう。
値を返すときは
getinstancevariable
がある。% ruby --dump=insns -e '@Inject' == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,7)> (catch: FALSE) 0000 getinstancevariable :@Inject, <is:0> ( 1)[Li] 0003 leave返さないときは
getinstancevariable
がない。% ruby --dump=insns -e '@Inject; nil' == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,12)> (catch: FALSE) 0000 putnil ( 1)[Li] 0001 leave残念ながら取れません。
RubyVM::AbstractSyntaxTree
InstructionSequenceにコンパイルされる前なら取れる。
% ruby --dump=parsetree -e '@Inject; nil' ########################################################### ## Do NOT use this node dump for any purpose other than ## ## debug and research. Compatibility is not guaranteed. ## ########################################################### # @ NODE_SCOPE (line: 1, location: (1,0)-(1,12)) # +- nd_tbl: (empty) # +- nd_args: # | (null node) # +- nd_body: # @ NODE_BLOCK (line: 1, location: (1,0)-(1,12)) # +- nd_head (1): # | @ NODE_IVAR (line: 1, location: (1,0)-(1,7))* # | +- nd_vid: :@Inject # +- nd_head (2): # @ NODE_NIL (line: 1, location: (1,9)-(1,12))*InstructionSequenceのコンパイル時にフックを仕掛けてASTを取得する
ASTの状態ならアノテーション(と勝手に呼んでいるただのインスタンス変数)が取れることがわかりました。
コンパイルされる前に@Inject
を取る方法を紹介します。
RubyVM::InstructionSequence.load_iseq
を使います。参考: YARV Maniacs 【第 13 回】 事前コンパイルへの道
.load_iseq
は引数でファイル名を取ります。
次のような感じでファイル名を出力したあとRubyVM::InstructionSequence.compile_file
でファイルをコンパイルし返す処理をprepend
で差し込んでみます。iseq_patch.rbiseq_patch = Module.new do def load_iseq(fname) p fname RubyVM::InstructionSequence.compile_file(fname) end end RubyVM::InstructionSequence.singleton_class.prepend(iseq_patch) pp trueフックできたようですね。
% ruby iseq_patch.rb "/home/sei/.rbenv/versions/myruby/lib/ruby/2.7.0/pp.rb" "/home/sei/.rbenv/versions/myruby/lib/ruby/2.7.0/prettyprint.rb" trueASTを取得してみます。
iseq_patch_to_get_ast.rbiseq_patch = Module.new do def load_iseq(fname) p RubyVM::AbstractSyntaxTree.parse_file(fname) RubyVM::InstructionSequence.compile_file(fname) end end RubyVM::InstructionSequence.singleton_class.prepend(iseq_patch) pp true無事ASTがとれました。
% ruby iseq_patch_to_get_ast.rb #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-593:3> #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-556:3> trueちなみに
super
は呼べません。iseq_patch_call_super.rbiseq_patch = Module.new do def load_iseq(fname) super end end RubyVM::InstructionSequence.singleton_class.prepend(iseq_patch) pp true% ruby iseq_patch_call_super.rb Traceback (most recent call last): 4: from iseq_patch_call_super.rb:8:in `<main>' 3: from <internal:prelude>:210:in `pp' 2: from /home/sei/.rbenv/versions/myruby/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:54:in `require' 1: from /home/sei/.rbenv/versions/myruby/lib/ruby/2.7.0/rubygems/core_ext/kernel_require.rb:54:in `require' iseq_patch_call_super.rb:3:in `load_iseq': super: no superclass method `load_iseq' for RubyVM::InstructionSequence:Class (NoMethodError)ASTから
@Inject
されているクラスの名前を取るなおnestingや上の階層のモジュールは考慮しないものとする。
Refinementsでおもむろに探索用のメソッドを生やすとべんり。
参考: akaza/ast_ext.rb at master · pocke/akaza
module Habu module AnnotationCollectorHelper refine(Object) do def traversable? false end end refine(RubyVM::AbstractSyntaxTree::Node) do def traversable? true end def traverse(&block) block.call(self) children.each { |child| child.traverse(&block) if child.traversable? } end end end endcase in用の
deconstruct
を生やしていい感じに探索する。module Habu module AnnotationCollectorHelper refine(RubyVM::AbstractSyntaxTree::Node) do def deconstruct [type, *children] end def each_constructor_annotations(&block) klass_name = nil inject_annotation_found = false traverse do |ast| case ast in :CLASS, [:COLON2, nil, klass_name], *_ # TODO: Support namespace in :IVAR, :@Inject inject_annotation_found = true in :DEFN, :initialize, *_ if inject_annotation_found == true block.call(klass_name) inject_annotation_found = false else inject_annotation_found = false end end end end end endこうすると直前に出てきたクラス定義のクラス名が
each_constructor_annotations
にわたしたブロックの引数として渡される。あとはいい感じに溜め込んでいけばよいだけ。load_iseqの中から
AnnnotationCollector
のインスタンスのインスタンス変数につっこんでいく。define_method
とmethod reference operatorを組み合わせるとこういう、prependしたモジュールから他のオブジェクトのインスタンスに副作用をおこすようなモジュールが手軽にかけてべんり。require 'habu/annotation_collector_helper' module Habu class AnnotationCollector using ::Habu::AnnotationCollectorHelper attr_reader :constructor_annotations def initialize @constructor_annotations = [] @iseq_interceptor = new_iseq_interceptor end def install RubyVM::InstructionSequence.singleton_class.prepend(@iseq_interceptor) end private def add_constructor_annotation(klass) @constructor_annotations << klass end def new_iseq_interceptor add_constructor_annotation = self.:add_constructor_annotation Module.new do define_method :load_iseq do |fname| ast = RubyVM::AbstractSyntaxTree.parse_file(fname) ast.each_constructor_annotations(&add_constructor_annotation) RubyVM::InstructionSequence.compile_file(fname) end end end end endこれの
install
はhabu/setup
のなかで行う。アノテーションの収集は実質グローバルなのでHabu.annotation_collector
経由でどこからでもアクセスできるようにしておく。require 'habu' module Habu module Setup class << self def install(annotation_collector) ::Habu.annotation_collector = annotation_collector ::Habu.annotation_collector.install end end end end Habu::Setup.install(::Habu::AnnotationCollector.new)DIコンテナの実装
定義・参照
雑にこうした。
#[]
は友達。module Habu class Container def initialize @factories = {} end def [](key, &block) if block @factories[key] = block else @factories.fetch(key).call(self) end end end end参照方法については
Habu::Container#cache
を用意して常に同じインスタンスを参照できるようにしたり、メソッドも合わせて定義したりとかやり方が色々ありそう。定義の方法についてはブロック渡す一択でよいと思う。#[]
以外のエイリアスを与えるのもあり。インスタンス化
メソッド引数との一致を見る。
module Habu class Container def new(klass, &block) params = klass.instance_method(:initialize).parameters klass.new(*params.filter_map { @1 == :req && self[@2] }, &block) end end endコンテナ経由でのインスタンス化は面倒
直接newして手軽に使えたほうがべんりなので
@Inject
されてるクラスのnew
を全部上書きしてコンテナに移譲していく。module Habu class Container def to_refinements refinements = Module.new refinements.instance_exec(self) do |container| Habu.annotation_collector.constructor_annotations.each do |klass_name| klass = const_get(klass_name) refine(klass.singleton_class) do define_method(:new) do |&block| container.new(klass, &block) end end end end refinements end end end将来的にやりたいこと
@Inject
されてるメソッドに関しては静的解析ではなくTracePointを使ってメソッド定義時に動的に取得したい- コンストラクタインジェクション以外のサポート
- メソッド呼び出し形式での参照のサポート
- シングルトンで参照するようなコンテナの追加
真似をしたいrubyでDIライブラリ
Habu実装したあとから調べてみると dry-container/dry-auto_inject がよさそうだた。
やっぱりcontainerに参照できるメソッドを生やすのはよさそう。API真似していきたい。
ただし個人的にはmixinしなくてもdelegate_to :container
みたいな形で移譲できるだけで十分なんじゃないかなと思う。https://dry-rb.org/gems/dry-container/
https://dry-rb.org/gems/dry-auto_inject/使用例
- dry-containerとdry-auto_injectでDIコンテナを作る - blog.kymmt.com
- Architecture of hanami applications - RubyKaigi 2018
所感
- habu gemを使うとRefinementsでいいかんじに依存性を注入できる
- Rubyのインスタンス変数は実質アノテーション
- Rubyではソースコード中に書かれた式がRubyVMで実行されるとは限らない
- RubyVMで実行されないような式もASTなら取れる
RubyVM::InstructionSequence.load_iseq
を上書きするとRubyVMがソースコードをコンパイルする前にフックできる#to_refinements
でRefinementsのモジュールを動的に作って返す手法はRubyキメた感があってよい最後に
rubyで
@Inject
でキメるgemを作ったので使ってみてください。いいなとおもったらStarボタンよろしくお願いします。
https://github.com/hanachin/habuQiitaの他にGitHubもやっているのでよかったらフォローしてください。
https://github.com/hanachin