- 投稿日:2019-05-27T23:55:38+09:00
Rubyで数独を解いてみた(しらみつぶし法)
はじめに
Ruby勉強のために、昔好きだった数独を深さ優先探索などを利用して解きました。回答のし易さを優先し、回答速度は犠牲にしてます。
数独のルール
① 9列あるタテのどの列にも1から9の数字が1つずつ入ります。
② 9行あるヨコのどの行にも1から9の数字が1つずつ入ります。
③ 太線で囲まれた3×3のブロック内にも1から9の数字が1つずつ入ります。引用 : 数独 ナンプレ の解き方
用語の説明
使う用語を統一しておきます。
- グリッド : セル全体を
- セル : 一つ一つのマス
- row : 行
- column : 行
- 正方形(square) : 3 × 3 のブロック
- セルの番号は、以下の図に対応させている
引用 : 盤面・セル・候補数字の表記法
問題
今回は、下記の問題を解くことにします。
出典:「数独通信 Vol.26」 P.4
次に、プログラムで解く下準備を説明します。
問題のフォーマット
問題の入力を簡単にするために、グリッド表示されている数独の問題は以下の形式に変換しておきます。
problem.txt.8.....1. 1..2..9.. ..7..4..3 3...1..9. ...7.2... .6..8...4 9..4..1.. ..4..3..5 .2.....8.問題と回答プログラムは別ファイルで管理
同階層に問題と回答プログラムは別ファイルで管理するようにします。
index.rb
(回答プログラム)problem.txt
(問題)なので、
problem.txt
には、図(出典:「数独通信 Vol.26」 P.4)に示した問題を変換したものを入れておきます。problem.txt.8.....1. 1..2..9.. ..7..4..3 3...1..9. ...7.2... .6..8...4 9..4..1.. ..4..3..5 .2.....8.
index.rb
には$ ruby index.rb problem.txt
とコマンドを打てば、別ファイル(
problem.txt
)の内容を、(繰り返し呼びだせる)ARGFオブジェクトとして呼び出せるようにしました。ARGF : スクリプトに指定した引数 (Object::ARGV を参照) をファイル名とみなして、 それらのファイルを連結した 1 つの仮想ファイルを表すオブジェクト
object ARGF (Ruby 2.6.0)index.rbARGF.each do |line| # 処理 end引数(
line
)には、problem.txt
に入っている1つ1つの数字や.
をオブジェクト(Enumerable::Enumerator オブジェクト)として格納され処理させるようにしました。解き方の方針
値が入ってないセルに入り得る値を入れてみて、しらみつぶしに検証していく方法で解きました。
(入るはずのない値は入れないようにしています)基本的な手順
- (1) 空のセルを選ぶ
- (2) そのセルに値を一つ入れる
- そのセルに入ることができる候補の値が少なく、かつセル番号が若い順に、値を入れていく
- (3) 数独のルールに従っているか検証
- 従ってる → (1)に戻る
- 従っていない → 別の値を試す
実際の全処理
実際の全処理は下記の用に書きました。
index.rbARGF.each do |line| line.chomp!print_grid(solve(make_grid(line.gsub(/\s/, '')))) end上に書いた各処理が連結されています。順を追って解説しますが、全てのプログラムを書いていきます。
index.rb# # param [Array] grid 1から9の値か、nilが81個並んだ配列 # return [Array] 改行とスペース消した値を9×9のgrid # def print_grid(grid, pad="\n") print (0..8).map{ |i| grid[9*i, 9].map{ |v| (v || ".") }.join("") }.join(pad), "\n" end # # param [String] string 1から9の値か、nilが81個並んだ一元配列 # return [Array] .をnilに変換 # def make_grid(string) string.split(//).map{ |c| c == "." ? nil : c.to_i } end # # param [Array] grid # param [Integer] cn セル番号 # return [Array] 該当する行に存在する値を配列としてを返す # def row(grid, cn) grid[9 * (cn / 9), 9] end # # param [Array] grid # param [Integer] cn セル番号 # return [Array] 該当する列に存在する値を配列としてを返す # def column(grid, cn) (0..8).map{ |k| grid[9 * k + cn % 9] } end # # param [Array] grid # param [Integer] cn セル番号 # return [Array] 該当する正方形に存在する値を配列としてを返す # def square(grid, cn) (0..8).map{ |k| grid[9*(3*(cn/9/3)+(k/3))+3*(cn%9/3)+(k%3)]} end # # param [Array] grid 1から9の値か、`nil`が81個並んだ配列 # return [Array] nil になっている位置をインデックス番号にして配列として返す # def empty_cell_numbers(grid) (0..80).reject{ |p| grid[p] } end # # param [Array] grid # param [Integer] cell_number # return [Array] 該当するセル番号の行、列、正方形に存在しない値を配列としてを返す # def possible_numbers(grid, cell_number) (1..9).to_a - fixed_numbers(grid, cell_number) end # # param [Array] grid # param [Integer] cell_number # return [Array] 該当するセル番号の行、列、正方形に存在する値を配列としてを返す # def fixed_numbers(grid, cell_number) ( row(grid, cell_number).compact | column(grid, cell_number).compact | square(grid, cell_number).compact ) end # # param [Array] grid # return [Array] grid 回答 # def solve(grid) empty_cell_numbers = empty_cell_numbers(grid) candidates = empty_cell_numbers.map{ |cell_number| [cell_number, possible_numbers(grid, cell_number)] } ordered_candidates = candidates.sort_by{ |cell| cell[1].length } if ordered_candidates.empty? print "\e[31m" # bash出力を赤色に puts 'complete↓' print "\e[0m" # bash出力の色を元に戻す grid else p ordered_candidates[0] cell_number, candidate_values = ordered_candidates[0] candidate_values.each do |value| grid[cell_number] = value sleep(0.02) print_grid(grid) puts "残り#{grid.count(nil)}個" puts '---------' return grid if solve(grid) end p "grid[#{cell_number}] => #{grid[cell_number]} 取り消し" grid[cell_number] = nil # 全て失敗したら未確定に戻す return false end end # # return [ARGF] 数独を表示 # ARGF.each do |line| line.chomp!print_grid(solve(make_grid(line.gsub(/\s/, '')))) end各メソッドの説明
問題が解かれる手順を説明しつつ、各メソッドの説明をします。基本的には、下記のメソッド内で別ファイルからデータを受け取って、ある処理をする中で、数独の問題を解いています。
ARGF.each do |line| line.chomp!print_grid(solve(make_grid(line.gsub(/\s/, '')))) end一つづつメソッドの概要を説明すると、
line.chomp!
- line(example) => ".8.....1. 1..2..9.. ..7..4..3 3...1..9. ...7.2... .6..8...4 9..4..1.. ..4..3..5 .2.....8."
- chomp : 文字列の末尾の改行文字を取り除いた新しい文字列を返す
print_grid()
- param [Array] grid 1から9の値か、
nil
が81個並んだ一元配列- return [Array] 改行とスペース消した値を
9×9
のgridsolve()
- param [Array] 1から9の値か、
nil
が81個並んだ一元配列
empty_cell_numbers()
- param [Array] grid 1から9の値か、
nil
が81個並んだ一元配列- return [Array]
nil
になっている位置をインデックス番号にして配列として返す- 変数 candidates に入るデータ
- index[0] : インデックス番号
- index[1] : セルに入ることができる値の候補
- => 配列 [index, [候補となる値(複数も可)]]で返す
- 変数 ordered_candidates に入るデータ
- 候補となる値の数が少ない順に並べる
- => 配列 [index, [候補となる値(複数も可)]]で返す
- 条件文 ordered_candidates.empty?
TRUE
- 配列の中身が空(全てのセルに1から9の値が入っている)なら
grid
を返すFALSE
- 配列に要素が在る場合、指定したindex番号のセルに、候補となる値を格納してメソッド
solve()
を再帰的に呼び出すmake_grid()
- param [String] 1から9の値か、
nil
が81個並んだ一元配列- return [Array]
.
をnilに変換line.gsub(/\s/, '')
- 引数
line
にあるスペースの削除変数
candidates
に関わる処理変数
candidates
に入るデータには、数独のルールに従っているかの条件をクリアしている値が、候補となる値として格納されます。関わらない処理を
...
で省略しました。# # param [Array] grid # param [Integer] cn セル番号 # return [Array] 該当する行に存在する値を配列としてを返す # def row(grid, cn) grid[9 * (cn / 9), 9] end # # param [Array] grid # param [Integer] cn セル番号 # return [Array] 該当する列に存在する値を配列としてを返す # def column(grid, cn) (0..8).map{ |k| grid[9 * k + cn % 9] } end # # param [Array] grid # param [Integer] cn セル番号 # return [Array] 該当する正方形に存在する値を配列としてを返す # def square(grid, cn) (0..8).map{ |k| grid[9*(3*(cn/9/3)+(k/3))+3*(cn%9/3)+(k%3)]} end # # param [Array] grid 1から9の値か、`nil`が81個並んだ配列 # return [Array] nil になっている位置をインデックス番号にして配列として返す # def empty_cell_numbers(grid) (0..80).reject{ |p| grid[p] } end # # param [Array] grid # param [Integer] cell_number # return [Array] 該当するセル番号の行、列、正方形に存在しない値を配列としてを返す # def possible_numbers(grid, cell_number) (1..9).to_a - fixed_numbers(grid, cell_number) end # # param [Array] grid # param [Integer] cell_number # return [Array] 該当するセル番号の行、列、正方形に存在する値を配列としてを返す # def fixed_numbers(grid, cell_number) ( row(grid, cell_number).compact | column(grid, cell_number).compact | square(grid, cell_number).compact ) end def solve(grid) ... candidates = empty_cell_numbers.map{ |cell_number| [cell_number, possible_numbers(grid, cell_number)] } ... endメソッド row の処理内容
該当する行に存在する値を配列としてを返します。
例えば、セル番号0の行は以下の配列を返します。
=> [nil, 8, nil, nil, nil, nil, nil, 1, nil]メソッド column の処理内容
該当する列に存在する値を配列としてを返します。
例えば、セル番号0の列は以下の配列を返します。
=> [nil, 1, nil, 3, nil, nil, 9, nil, nil]メソッド square の処理内容
該当する正方形に存在する値を配列としてを返します。
例えば、セル番号0の正方形は以下の配列を返します。
=> [nil, 8, nil, 1, nil, nil, nil, nil, 7]メソッド possible_numbers の処理内容
該当するセル番号の行、列、正方形に存在しない値を配列としてを返します。
例えば、セル番号0の場合は以下の配列を返します。
=> [2, 4, 5, 6]メソッド fixed_numbers の処理内容
該当するセル番号の行、列、正方形に存在する値を配列としてを返します。
例えば、セル番号0の場合は以下の配列を返します。
=> [8, 1, 3, 9, 7]上記のような、様々なメソッドを経て、数独の条件に合う値を選定しています。
答え合わせ
0.2秒間隔でセルに値を入力していく映像が以下になります。
数独のチェックツール(ナンプレ解答プログラム)を使って、問題がないか確認できました。
パフォーマンス
ruby index.rb problem.txt 0.36 real # コマンドを実行するためにかかった時間 0.20 user 0.07 sys
勉強になったこと
- 配列処理系のメソッドの挙動の理解(
each
,select
,reject
,map
,compact
)- アルゴリズム(深さ優先探索)
次回やること
- 数独の基本解法(ブロッケン, レッツミー, マスミ,いずれにしても理論,予約 など)のプログラムを実装し解いてパフォーマンスを改善させたい
- しらみつぶし法のデメリットは、問題によってはとても処理が長くなる場合がある
- 最終的には、部分解答して、最適な基本解法を選択する、いわゆる「解き筋」をモデル化して解きたい
参考
- 投稿日:2019-05-27T23:02:08+09:00
Railsで基本情報技術者試験の過去問題サイトを作る(4:親子関係、参照編)
はじめに
ゆる〜く学ぶ。みんなのWeb勉強コミュニティー。 「にゅ〜ぶる会」を運用中です。
https://newburu.github.io/そこで、何か教育用のコンテンツが欲しいなぁ〜と思い立ち、今回の企画をスタートしました!
Railsで基本情報技術者試験の過去問題サイトを作ります!
最終目標
- 問題・回答の登録は、Scaffoldで簡易でOK
- APIを用意して、ランダムに問題を抽出する機能を追加する
- TwitterBOT、LINEBOT、SlackBOTが出来たら良いな
履歴
1:構築編
https://qiita.com/newburu/items/ed59f47ac645b19620f6
2:日本語化(i18n)編
https://qiita.com/newburu/items/4f12fdb61bf6cd601545/
3:親子関係、登録編
https://qiita.com/newburu/items/f2a20289be5ec1fc1b77
4:親子関係、参照編
本ページ今回やる事
- 登録した親子関係の情報を参照系の画面に表示します
※レイアウトをやろうと思いましたが、こちらの方が優先なので、予定を変更させて頂きました。
登録した親子関係の情報を参照系の画面に表示します
1. まずは、現状を確認しましょう。
2. 問題参照画面のViewを直します。
問題参照画面に、回答一覧を追加するよ!
元ソース
app/views/questions/show.html.slimp#notice = notice p strong = "#{Question.human_attribute_name(:category1)}:" = @question.category1 p strong = "#{Question.human_attribute_name(:category2)}:" = @question.category2 p strong = "#{Question.human_attribute_name(:category3)}:" = @question.category3 p strong = "#{Question.human_attribute_name(:msg)}:" = @question.msg // ここに回答一覧を足していきます。 => link_to t('btn.edit'), edit_question_path(@question) '| =< link_to t('btn.back'), questions_path修正後ソース
app/views/questions/show.html.slimp#notice = notice p strong = "#{Question.human_attribute_name(:category1)}:" = @question.category1 p strong = "#{Question.human_attribute_name(:category2)}:" = @question.category2 p strong = "#{Question.human_attribute_name(:category3)}:" = @question.category3 p strong = "#{Question.human_attribute_name(:msg)}:" = @question.msg // ここに回答一覧を足します。 p strong = "#{Question.human_attribute_name(:answers)}:" // 問題の子供として、has_manyしているanswersでループ(each)します。そして、その値(回答モデル)はanswerに渡します。 - @question.answers.each do |answer| p strong = "#{Answer.human_attribute_name(:msg)}:" = answer.msg p strong = "#{Answer.human_attribute_name(:correct)}:" = answer.correct => link_to t('btn.edit'), edit_question_path(@question) '| =< link_to t('btn.back'), questions_path完成した画面
今回はここまで
ありがとうございました!
画面周りは、まだまだ綺麗にする要素はいっぱいあるけど、
次回は、API化していこうかな。
- 投稿日:2019-05-27T21:32:48+09:00
rubyでブラック・ジャックできるんです!!
今日はこちらの記事を参考にしてトランプのブラック・ジャックを作ってみました。
仕様
・初期カードは52枚。引く際にカードの重複は無いようにする
・プレイヤーとディーラーの2人対戦。プレイヤーは実行者、ディーラーは自動的に実行・
・実行開始時、プレイヤーとディーラーはそれぞれ、カードを2枚引く。引いたカードは画面に表示する。ただし、ディーラーの2枚目のカードは分からないようにする
・その後、先にプレイヤーがカードを引く。プレイヤーが21を超えていたらバースト、その時点でゲーム終了
・プレイヤーは、カードを引くたびに、次のカードを引くか選択できる
・プレイヤーが引き終えたら、その後ディーラーは、自分の手札が17以上になるまで引き続ける
・プレイヤーとディーラーが引き終えたら勝負。より21に近い方の勝ち
・JとQとKは10として扱う
・Aはとりあえず「1」としてだけ扱う。「11」にはしない
・ダブルダウンなし、スプリットなし、サレンダーなし、その他特殊そうなルールなしコード
view.rbrequire './black_jack/player' require './black_jack/card' require './black_jack/controller' p '今からブラックジャックを始めます' p 'ディーラーとプレイヤーに2枚ずつカードを配ります。' @dealer = create_player @player = create_player p "ディーラーの#{@dealer.hand_index(0)}" p "あなたの#{@player.hand_index(0)}" p "あなたの#{@player.hand_index(1)}" 2.upto(15) do |i| p 'カードを引きますか?(y/n)' input = gets.chomp if input == 'y' @player.draw p "あなたの引いたカードは#{@player.hand[i][0]}の#{@player.hand[i][1]}です" else break end if @player.card_sum > 21 p '合計が21を超えました!!!' p 'プレイヤーの負けです!!!' exit end end p 'では、次にディーラーが引いていきたいと思います。' while @dealer.card_sum < 17 @dealer.draw end p '両者引き終わりました。' p '勝者は....' p @player.battle(@dealer) p 'プレイヤーのカード' p @player.card_index p 'ディーラーのカード' p @dealer.card_indexlogin.rbrequire './black_jack/player' require './black_jack/card' def create_player @player = Player.new @player.draw @player.draw @player endcard.rbclass Card MARK = ['spade', 'heart', 'diamond', 'club'].freeze NUMBER = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 ].freeze attr_reader :mark, :number def initialize(mark,number) @mark = mark @number = number end endplayer.rbclass Player attr_reader :hand # 引いたカードを入れる配列 def initialize @hand = [] end # カードを引く処理。既に持っているカードを引いたらやり直し def draw @hand.uniq! card = Card.new(Card::MARK.sample, Card::NUMBER.sample) @hand << [card.mark, card.number] unless @hand.count == @hand.uniq.count draw end @hand end # 手札の内容 def hand_index(i) "#{i + 1}枚目は#{self.hand[i][0]}の#{self.hand[i][1]}です" end # プレイヤーのカードの合計がディーラーのカードの合計と比較する def battle(other) result = (card_sum - 21).abs <=> (other.card_sum - 21).abs if result == -1 'プレイヤーです!!!' elsif result == 0 '引き分けです!!!' else 'ディーラーです!!!' end end # カードの合計を出す。 def card_sum sum = 0 @hand.each do |num| if num[1] > 10 sum += 10 else sum += num[1] end end sum end # 手札の一覧を返す def card_index hand.map { |num| "#{num[0]}の#{num[1]}" }.sort.join(',') end end完成動画
実際にやってみると以下のような動画になります(リンクですいません)。トランプのブラック・ジャックを作った!!!
— あるくまくん (@kesuike713) 2019年5月26日
ルール合っているか分からないけど!!! pic.twitter.com/UAl8mQfCmvまとめ
今回は処理と出力で別のファイルにすることを特に意識しました。
実際に出力されるのはview.rbに、処理はその他のファイルに書きました。
ただ、create_playerメソッドの為だけにlogic.rbを作る必要はなかったかな〜と反省中です。
本当はplayerのインスタンスが作成された時点でdrawメソッドを2回実行させたかったけど、どうすればいいか分かりませんでした汗
後、view.rbに少しだけ処理が残ってしまいました。
こちらも今後リファクタリング 必須ですね。ブラック・ジャックはこちらの記事にも書いてある通り、基本的な文法が理解出来ていれば出来るので、ぜひやってみてください!!!!
- 投稿日:2019-05-27T21:04:17+09:00
【Rails】nestされたcontrollerへのform_withの書き方
namespaceでnestされたcontroller(Admin::Userなど)のアクション宛てformを
form_with
で記述したい場合の雛形になります。書き方
例えば、newのviewから
Amin::Post
のcreate
アクションへ送りたい場合、new.html.erb<%= form_with scope: :post, url: admin_posts_path, local: true do |f| %> <%= f.text_field :title %> <% end %>
scope:
とurl:
により、htmlレベルでは以下のようになります。
↓<form action="/admin/posts" method="post" > <input type="text" name="post[title]"> </form>◆ちなみに、scope: :postをなくすと
new.html.erb<%= form_with url: admin_posts_path, local: true do |f| %> <%= f.text_field :title %> <% end %>↓
<form action="/admin/posts" method="post" > <input type="text" name="title"> </form>controller側でストロングパラメーターを入れていれば↑は弾かれますね。
admin/posts_controller.rbprivate # ストロングパラメーター def post_params params.require(:post).permit(:title, :description) end最後に
…例を見直すと、
controllerのpost
とmethodのpost
という2つのpostが出てくるので、ややこしい構成になってた。。。
(最近の松本人志が怒るやつや・・・)
- 投稿日:2019-05-27T20:28:04+09:00
テストで気づいたこと
- 投稿日:2019-05-27T19:52:55+09:00
WSL上のUbuntu環境で chromedriver を使ってRSpecするとエラーが発生するので、その解決方法。
はじめに
WSLでUbutu環境を使いchromedriverでRSpecテストをしたらエラーが発生しましたが、無事に解決できたのでその方法をまとめます。
Railsで有名な参考書「現場で使える Ruby on Rails 5速習実践ガイド」をやっていてぶつかったエラーなので、参考になる方が多いのではと思いQiitaで記事にまとめようと思いました。
おそらく、Windows環境で参考書通りにWSLとUbuntuを使っている人だと、ほぼ確実にぶつかるエラーなので参考になれば嬉しいです。
環境とバージョン
- WSL
- Ubuntu 18.04.2 LTS
- Ruby 2.5.1
- Rails 5.2.3
- RSpec-rails 3.8.2
エラー内容と解決方法
RSpecを記述した後、テストを通すとエラーが発生しました。(現場Rails205p)
エラー内容としては「こちらの記事(teratail)」と全く同じものです。
テスト内容の記述や
spec_helper.rb
、rails_helper.rb
のソースコードもこちらの記事と同じなので、必要に応じて参考にしてみてください。エラー内容Failures: 1) タスク管理機能 一覧表示機能 ユーザーAがログインしているとき ユーザーAが作成したタスクが表示される Got 0 failures and 2 other errors: 1.1) Failure/Error: visit login_path Selenium::WebDriver::Error::WebDriverError: unable to connect to chromedriver 127.0.0.1:9515 # ./spec/system/tasks_spec.rb:12:in `block (4 levels) in <top (required)>' 1.2) Failure/Error: raise Error::WebDriverError, cannot_connect_error_text Selenium::WebDriver::Error::WebDriverError: unable to connect to chromedriver 127.0.0.1:9515エラーを読むと、どうやらwebdriverにエラーが発生しててchromedriverに接続できないみたいです。
Windows側とは別に、Ubuntu側にもchromeをインストールしないといけないみたいなので、こちらの記事「ubuntuにchromeを簡単にインストールしよう」を参考にchromeをインストールしました。
ちなみに、Ubuntu上で以下のコマンドを入力するとchromeがインストールされているか分かります。
$ google-chrome -version Google Chrome 74.0.3729.169さて、Ubuntu上にもchromeがインストールできたということで、もう一度テストを実行すると次は以下のエラーが...。
エラー内容Failures: 1) タスク管理機能 一覧表示機能 ユーザーAがログインしているとき ユーザーAが作成したタスクが表示される Got 0 failures and 2 other errors: 1.1) Failure/Error: visit login_path Net::ReadTimeout: Net::ReadTimeout # ./spec/system/tasks_spec.rb:12:in `block (4 levels) in <top (required)>' 1.2) Failure/Error: @io.to_io.wait_readable(@read_timeout) or raise Net::ReadTimeout Net::ReadTimeout: Net::ReadTimeoutどうやら、次はchromeに繋がらずにタイムアウトしてしまったようですね。
こちらの記事(WSL Ubuntu 上で chromedriver を使った System Spec を動かす)によれば、
ChromeがインストールされているのはWSLの外のWindows環境。 なのでパスが通っていないし、バイナリも不一致となってしまう。
そのため cannot find Chrome binary のようなエラーが出るなどして System Spec を実行できない。
以下の2種類の対策方法がありそうだ。
A. chromedriver-helper を使わずに、自分で chromedriver を設定する
B. chromedriver-helper を利用しつつ、ChromeをWSL環境にインストールする
対策A. chromedriver-helper を使わずに、自分で chromedriver を設定するとのこと。WSLでUbuntuを使うとパスが通ってないのでいろいろと設定が追加で必要なようです。
二つの対策方法があるみたいなので、記事を参考に僕もAの「自分でchromedriverを設定する」ことにしました。
結果、無事にテストを通すことができました。
めでたし、めでたし参考記事まとめ
- 投稿日:2019-05-27T19:17:38+09:00
Herokuデプロイ後のエラー
Cloud9上では、問題のなかったページ移行の動作ですが、
Herokuにデプロイ後、Heroku上で確認すると以下のエラーメッセージが出てきてしまいます。
(例えば、loginページからログインし、tasks(タスク一覧ページ)に行こうとすると以下のエラーになってしまう。)
We're sorry, but something went wrong.
If you are the application owner check the logs for more information.行った対処
☆ マイグレーションの再実行
① rails db:migrate:reset
意味 : 一度DBを削除して、作成し直し、もう一度マイグレーションを実行。結果 : 一部動作はクリアしたが、まだエラーが生じる。
注: マイグレーションファイルの内容を大幅に変えていたので、Heroku側のデータベースは一旦リセットする。
① heroku pg:reset DATABASE
② To proceed, type xxxxxxxxxxxx or re-run this command with --confirm xxxxxxxxxxxxアプリ名を入力(xxxxxxxxxxxxの箇所)
③ heroku run rails db:migrate
意味 : テーブルを再作成する。これでも改善できなかったら、、、
① heroku restart
意味 : herokuのサーバも再起動。これで何とかエラーは消滅しました。
これを解決するのに2日ほどかかりました。
初心者には結構キツイですね、、
- 投稿日:2019-05-27T19:09:24+09:00
ホーム以外でホームの Rake タスクを実行
ホームディレクトリーに Rakefile を置いて,いろいろな作業ができるようにしている,とする。
別のディレクトリーにいるときに,そのタスクを実行するにはどうするか,というのがこの記事のテーマ。
-f オプションで指定
rake
コマンドには,Rake ファイルを指定する-f
オプションがあるので,rake -f ~/Rakefile foo
のようにすればいい。
でもちょっと打つのが面倒じゃない? もう少し簡略化できないの?
-f
オプションにはディレクトリーを渡すことはできないが,ワイルドカードは使えるので,rake -f ~/* foo
と書けばよいようだ。
おまけ
Windows の場合
Windows では
~
がホームディレクトリーを表してはくれないので,この手は使えない。残念。
これなんか,rake コマンド側で対処してくれたらいいのにねー。
もちろん Windows では~
という名前のディレクトリーを作ることができてしまうので,原理的には曖昧性が生じるわけだけど,「-f
オプションの~
はホーム」ってルールにしちゃってもいいんじゃない?Rake ファイルのファイル名
Rake ファイルのファイル名は
- Rakefile
- Rakefile.rb
のどっちでもいいし,大文字・小文字も気にしなくていい。
Rake タスク中のパスの扱い方
Rake ファイルの場所がカレントディレクトリーと一致しているとは限らない,ということは常に頭に入れておきたい。
-f
を使わなくても不一致は起こりうる。
カレントディレクトリーに Rake ファイルが存在しない場合,ディレクトリー階層を上にたどって Rake ファイルを探すからだ。となると,Rake ファイル中で,たとえば
require "./my_library"みたいな記述をしてしまってはダメ,ということになる。
この.
はカレントディレクトリーを表しているからね。
Rake ファイルと同じ場所にあるmy_library.rb
を読み込ませたいならrequire_relative "my_library"とすればいい。
また,ファイルの読み書きなどでも
IO.read("foo.txt")みたいなことはやめて,
__dir__
メソッドを使って Rake ファイルの置かれたディレクトリーのパスを取得し,そこからの相対パスでファイルを指定するようにしたい。
- 投稿日:2019-05-27T19:02:55+09:00
モデルを継承して値を取得したい
IBeacon(子) < ShopTerminal(親)
モデル間の継承をしたいとする
継承とは
簡単に言うと、親のモデルに定義されている内容は全部子モデルでも使えるよ〜ってこと
値の取得
で、今回親のモデルの内容を子モデルを使って取得したい と言うこと
コントローラでいつものように値を取得したいけどなぜか
beacon = IBeacon.find_by_uuid(params[:uuid])
でbeaconの値が[ ]に。
beacon = ShopTerminal.find_by_uuid(params[:uuid])
だとちゃんと値はいってるのに。。。beacons_controller.rbclass Api::BeaconsController < Api::AbstractSystemController def show return render_error('Please set uuid', 400) unless params[:uuid].present? beacon = IBeacon.find_by_uuid(params[:uuid]) return render_error('beacon not found', 400) unless beacon.present? render json: beacon, serializer: IBeaconSerializer, root: nil end end解決策
親モデルのShopTerminalにtypeカラム(String型)を追加
そして追加されたtypeカラムの中に
IBeacon
と書くことで上記のコード通り、子モデルでの値の取得ができるようになった!!今回やったことはSTIと言うらしい
typeという語はテーブルでSTI(Single Table Inheritance)を指定するために予約されている予約語で、この場合以外に使ったらエラーになるので気をつける。
STI(単一テーブル継承)はACtiveRecoredがサポートしている機能です。
- 投稿日:2019-05-27T18:37:30+09:00
【初学者向け】メールアドレスの正規表現について一番細かく解説する
はじめに
Railsチュートリアル などで学習していると、以下のような正規表現が使われているのを見たことがあると思います。
/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
これは、アプリのユーザ登録やログイン時に入力するメールアドレスが正しい形式になっているかを検証する時に用いる正規表現で、筆者が受講していたプログラミングスクールの教材でも同じものが登場していました。
しかし、ほとんどの教材ではこの正規表現について細かく解説されていません。(主観)
例えばRailsチュートリアルには以下のように書かれていますし、この正規表現を理解するために、お手頃なサイズに分割して表 6.1にまとめました。
某スクールの教材では、「(今は)理解する必要はありません」と書かれています。
(これらの教材での学習のメインはRailsなので、正規表現について細かく解説されていないことが悪いと言いたいわけではありません!)筆者も最初は、お手頃なサイズに分割された解説を見て理解した気でいましたが、改めて見てみると、細かいところまでしっかり理解していないことに気づきました。
もちろん、正規表現に関する書籍を読んだりして完全に理解すればよい話なのですが、とりあえずメールアドレスの正規表現だけでも理解しようと思い学習してみると、意外と一癖あるということが分かりました。(後述する\-
とか)そこで、(僭越ながら)このメールアドレスの正規表現について、お手頃なサイズをさらに分割して、一番細かく解説していきます。
今はまだ理解しなくていいやと思っている駆け出しエンジニアや、チュートリアルなどの解説だけを見て理解した気になってしまっている駆け出しエンジニアに届けばなーと思っています。※正規表現の基本的な記法は同じですが、使用する言語によって細かいところが若干異なります。今回はRubyにおける書き方で進めていきます。
解説
お手頃なサイズ
まずは先述の「お手頃なサイズに分割」された解説を下に示します。Railsチュートリアル から引用。
正規表現 意味 /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i (完全な正規表現) / 正規表現の開始を示す \A 文字列の先頭 [\w+\-.]+ 英数字、アンダースコア (_)、プラス (+)、ハイフン (-)、ドット (.) のいずれかを少なくとも1文字以上繰り返す @ アットマーク [a-z\d\-.]+ 英小文字、数字、ハイフン、ドットのいずれかを少なくとも1文字以上繰り返す \. ドット [a-z]+ 英小文字を少なくとも1文字以上繰り返す \z 文字列の末尾 / 正規表現の終わりを示す i 大文字小文字を無視するオプション 細かく解説
/
正規表現の開始を示す。
(言語によって記述は異なる。Rubyではこれ。)\A
文字列の先頭。
以下の記事にとっっっっても分かりやすく説明が書かれています。
→Railsの正規表現でよく使われる \A \z って何??[\w+\-.]+
\w
aからzまでの英小文字、AからZまでの英大文字、0から9までの数字、アンダースコア(_)のどれか1文字。
+
文字としてのプラス
+
。
[ ]
の外では意味を持つ(後述)が、中では意味を持たないただの文字。\-
文字としてのハイフン
-
。
[ ]
の中では意味を持ってしまう(後述)ため、\
を前に付けることで意味を持たなくして(エスケープ)ただの文字扱いにする。.
文字としてのドット
.
。
[ ]
の外では意味を持つ(後述)が、中では意味を持たないただの文字。[ ]
[ ]
の中のどれか1文字。+
直前のパターンの1回以上の繰り返し。
[ ]
の外なので意味を持つ。
今回の直前のパターンは、[ ]
の中身。つまり
\w
英数字、アンダースコア (_)、
+
プラス (+)、
\-
ハイフン (-)、
.
ドット (.)
[ ]
のいずれか
+
を少なくとも1文字以上繰り返すメールアドレス
○○○@△△△.***
の○○○
の部分に当たります。(例:hoge, h-o.g+e, Ho_Ge)@
文字としての
@
。メールアドレス
○○○@△△△.***
の@
の部分に当たります。[a-z\d\-.]+
a-z
aからzまでの英小文字のどれか1文字。ここでの
-
は範囲の意味を持つ。
[ ]
の中の-
は意味を持つため、エスケープの方法として先述の\
があったが、
[-abc]
や[abc-]
のように、最初か最後に-
を置くことで、その-
に意味を持たなくさせることも可能。
(cf. 正規表現リファレンス(CoffeeScript))\d
0から9までの数字のどれか1文字。
\-
文字としてのハイフン
-
。(既出).
文字としてのドット
.
。(既出)[ ]
[ ]
の中のどれか1文字。(既出)+
直前のパターンの1回以上の繰り返し。(既出)
つまり
a-z
英小文字、
\d
数字、
\-
ハイフン、
.
ドット
[ ]
のいずれか
+
を少なくとも1文字以上繰り返すメールアドレス
○○○@△△△.***
の△△△
の部分に当たります。(例:gmail, docomo.ne)\.
文字としてのドット
.
。
[ ]
の外では「任意の1文字」という意味を持つため、エスケープのために\
を付ける。メールアドレス
○○○@△△△.***
の.
の部分に当たります。[a-z]+
a-z
aからzまでの英小文字のどれか1文字。(既出)
[ ]
[ ]
の中のどれか1文字。(既出)+
直前のパターンの1回以上の繰り返し。(既出)
つまり
a-z
英小文字
[ ]
(のいずれか)
+
を少なくとも1文字以上繰り返すメールアドレス
○○○@△△△.***
の***
の部分に当たります。(例:com, jp)\z
文字列の末尾。
大文字の\Z
だと意味が変わってしまうので注意。
(cf. Railsの正規表現でよく使われる \A \z って何??)/
正規表現の終わりを示す。
i
大文字と小文字を区別しない。
(cf. 大文字と小文字を区別せずにマッチを行う(/i修飾子))まとめ
ふぃー。お疲れ様でした。
このように、細かく分割してみると、お手頃なサイズで理解していたつもりがほとんど理解できていなかったことに気づくと思います。では、改めてメールアドレスの正規表現を見てみましょう。
/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
もうどういう意味かすぐに分かりますね!
しかし、正規表現の用途はメールアドレスだけではありません。
正規表現にはもっともっといろいろな記法があって、いろんな表現ができるので、気になった方はどんどん調べてみてください!
伊藤淳一さん(@jnchito)のQiita記事がとてもわかりやすくオススメです!
→初心者歓迎!手と目で覚える正規表現入門・その1「さまざまな形式の電話番号を検索しよう」最後に
今回の正規表現に限った話ではなく、様々な教材や書籍において、そこではそれほど重要ではない内容について細かく解説されていないのは当然のことなので、少しでも「?」と思ったらすぐに調べてみる癖をつけたいものです…(戒め)
- 投稿日:2019-05-27T17:05:50+09:00
RubyのバージョンがHeroku未対応だった
はじめに
Rails初心者です。
主にBundlerの扱いにつまずいたのでメモします。
誤った点がありましたら、ご指摘いただけると勉強になります。補足
Rubyの
2.7.0
は2019年5月の段階ではまだリリースされていませんでした。
最新バージョンは2.6.3
でしたので修正しました。つまづいたところ
Rubyのバージョン
2.7.0
を使ってHerokuにデプロイしたところ、
エラーが発生しました。調べてみると、
Heroku側がまだこのバージョンに対応していないとのことでした。https://devcenter.heroku.com/articles/ruby-support#ruby-versions
対応しているのが
2.6.3
までですね。
対応したバージョンが使えるように、Rubyのバージョンを変えていきます。ちなみに下の「MRI」のところには例として、
・Ruby2.6.14.4を入れたら、2.4.6になるよ ・2.7.6.2を入れたら、2.5.5だよ ・3.0.3を入れたら、2.6.3だよと書かれています。
Rubyのバージョンを変更します。
試しに今回は、2.6.0
をインストールしてみます。解決策
①使うRubyのバージョンをインストール
こちらが参考になります。
②bundlerインストール
$ gem install bundler③Gemfileに記載
source 'https://rubygems.org' ruby '2.6.0' #省略④Gemfile.lockファイルがあれば消す
Gemfile.lock
はbundle install
する度に新しく作られるので、
ファイルごと消しておきます。⑤インストール
$ bundle install⑥コミット
新しい
Gemfile.lock
が作成されたのを確認し、
コミット→プッシュ$ git commit $ git push heroku無事、Herokuに反映されました!
- 投稿日:2019-05-27T16:00:04+09:00
[Ruby]レーシバ自身を変更するとは?
概要
配列からnilである要素を取り除いた新しい要素を返すメソッドcompactですが、compact!となにが違うのかとおもいました。
リファレンスには「!」がつくほうはレシーバ自身を変更するメソッドとありました。
では、「レシーバ自身を変更する」とはどういうことなのでしょうか
レシーバとは
レシーバとはメソッドを実行するオブジェクトのことです。
下記だと、.の左側のarrayです。array.compact array.compact!レシーバ自身を変更するとは
レシーバとはメソッドを実行するオブジェクトのことでしたので、「レシーバ自身を変更する」とは、このオブジェクトが変更されるということになります。
object_idをみてみます。
sample.rbfruits = ["apple", nil, "orange", nil, nil, "banana"] puts fruits.object_id fruits = fruits.compact puts fruits.object_idターミナル70311962445680 70311962445540まずは「!」がないほうですが、idが違います。
つまり、配列fruitsから、compactメソッドによりnilを除いた新しい配列fruitsができたことになります。次は「!」をつけます。
sample.rbfruits = ["apple", nil, "orange", nil, nil, "banana"] puts fruits.object_id fruits = fruits.compact! puts fruits.object_idターミナル70332086281520 70332086281520idが同じです。
つまり、配列fruits自身が変更されたことになります。これがレシーバ自身(今回でいうとfruits)を変更するの正体でした。
まとめ
レシーバ自身を変更とは破壊的メソッドということでした。
今回は「!」をつけるつけないで、参照元から変更するかしないかを選択できます。
「!」をつけ参照元から変更するメリットは、メモリの節約ってところでしょうか。
- 投稿日:2019-05-27T13:36:41+09:00
Array#injectでArray#sumを代用
Array#sum
はRuby2.4から、それより前のバージョンでArray#sum
っぽいことがしたい時はArray#inject
で代用可能。[1,2,3,4,5].inject(:+)参考
http://rurema.clear-code.com/query:sum/
http://rurema.clear-code.com/2.3.0/method/Enumerable/i/inject.html
- 投稿日:2019-05-27T13:18:33+09:00
RailsにオープンソースECのSpreeを導入と注意点(2019年度版)
Railsに導入できるオープンソースのECというとSpreeかSolidusがあります。今回導入したので自分の備忘録代わりにメモ残します。随時追記予定です。
目的&目標
現在動作しているRailsのアプリにEC機能を搭載しようとしてます。
サービス側と管理側で別アプリになっているのでECとしての機能を同様に分けて実装します。前提条件
以下は諸々Spreeの設定にあたってベストと思われる状態にあえて揃えました(2019/4現在)。
Ruby 2.5.0
Rails 5.2.1
MySQL 5.7.25
※上記全てAWSにて用意
Spreeのインストール
本家サイトも参照すること
https://github.com/spree/spreeGemfileに以下を設定
gem 'spree', '~> 3.7.0' gem 'spree_auth_devise', '~> 3.5' gem 'spree_gateway', '~> 3.4'
bundle install
とrake db:migrate
したら以下のジェネレーターを実行して必要な初期設定を行います。rails g spree:install --user_class=Spree::User rails g spree:auth:install rails g spree_gateway:installconfig/routes.rbにマウント先を設定します。
mount Spree::Core::Engine, at: '/'ここの/のところにディレクトリ名を入れればそこにマウントされます。
各種設定
ImageMagickの設定
brew install imagemagick@6 PKG_CONFIG_PATH=/usr/local/opt/imagemagick@6/lib/pkgconfig gem install rmagick -v '2.16.0’ (Gemfileのrmagickと合わせる)ロゴの設定
config/initializers/spree.rb に以下を設定
Spree.config do |config| config.logo = "https://myapp.com/assets/logo_thumb.png" endユーザ認証機能の統一
config/initializers/spree.rb に対象のモデルを設定
Spree.config do |config| Spree.user_class = "User" endlib/spree/authentication_helpers.rb に以下を記載。
ちなみに自分のアプリへのRoutingにはmain_appという接頭ワードをつける必要あり。実質自分のアプリへの誘導などを指定しているのでヘルパーの内容をよく確認すること。module Spree module CurrentUserHelpers def self.included(receiver) receiver.send :helper_method, :spree_current_user end def spree_current_user current_user end end module AuthenticationHelpers def self.included(receiver) receiver.send :helper_method, :spree_login_path receiver.send :helper_method, :spree_signup_path receiver.send :helper_method, :spree_logout_path end def spree_login_path main_app.login_path end def spree_signup_path main_app.signup_path end def spree_logout_path main_app.logout_path end end end ApplicationController.include Spree::AuthenticationHelpers ApplicationController.include Spree::CurrentUserHelpers Spree::Api::BaseController.include Spree::CurrentUserHelpers管理画面用ディレクトリの設定
おなじみspree.rbに以下を追加
config.admin_path = "/your_admin"多言語設定
Gemfileに以下を記載
gem 'spree_i18n', github: 'spree-contrib/spree_i18n' gem 'spree_globalize', github: 'spree-contrib/spree_globalize'config/initializers/spree.rb に以下を設定
Spree.config do |config| Spree::Config[:currency] = 'JPY' SpreeI18n::Config.available_locales = [:en, :ja] SpreeGlobalize::Config.supported_locales = [:en, :ja] end決済にStripe導入
日本で決済機能を導入するならこれ一択です。
こちらからアカウント登録して、
https://stripe.com/ja-us
管理画面の設定>支払い方法からSpree::Gateway::StripeGatewayを選択してテスト用のPKとSKを設定すればOKです。本番用はPKとSKの本番用があるのでそちらに切り替えればOK。トライして難しかったこと
独自に決済機能の実装
実はStripeよりPayJPの方が決済手数料が安い(特にStartup向けには)ので使いたかったのだけど、既存のGatewayを拡張してインプリメントしないといけないこともあり断念。
(CTOえふしんさん曰くStripeから拡張して開発しているぽいのでStripeGatewayから拡張するのが吉かも)注意点
SpreeのSlackコミュニティは頼りにならない
Laravelとか他のオープンソースコミュニティでも盛んですがSpreeにもSlackコミュニティがあります。
http://slack.spreecommerce.com/
しかし意外と質問投げても返ってこない(わかりやすいものは回答してくれる可能性あり)ので頼りにしないことが注意です。各種Extensionは意外と使えない
Extensionディレクトリ
https://github.com/spree-contrib
Reviewを追加するものとInvoice作成するものを使おうとしましたが既存のアプリを破壊してしまうようだったので利用を停止しました。基本的にGemfileに追加してジェネレータを動かす+migration動かすというのが通常のインプリメントなのですが既存アプリに影響ないかは慎重に確認することをオススメします。
特にmigrationファイルにはUserモデルやReviewモデルに何か追加するようなものもあり確認しないと危険です。モデル、コントローラーは都度オーバライドする
ほとんどの機能はGemのソースのものをそのまま利用することになりますが、独自の挙動を入れたい場合はオーバーライドします。
vender/bundle配下にSpreeのGemが色々入っています(Spree_frontendとかSpree_coreとか)のでここから該当のソースを見つけて自分のアプリのapp配下に同じディレクトリ構造(spreeディレクトリをcontroller, model配下に作成してオーバーライド)にしてコピーしたソースを修正します。わからなくなったら動くデモを参照する
素のRailsアプリにSpree入れたサンプルがあるので動作確認や何が正しいのかを見たければこちらを参考にすること。
https://spreecommerce-demo.herokuapp.com/add-to-cartボタンが押せない
Spreeと関係ないページにカートに追加ボタン実装とかするとボタンが押せないことがあります。Spreeのテンプレートとかパスが通ってないことが原因なのですがおそらく以下で解決するかと思います。
対象のViewファイルに以下を足します(Slimの例)。= render 'spree/shared/paths' = javascript_include_tag 'spree/frontend/all' = render 'spree/shared/translations'カート決済で住所入力から先に進まない
管理画面でゾーン、支払い方法、配送方法などちゃんと設定しないと内部エラーになります。なので管理画面の設定を見てみてください。
引き続きの課題
多言語の処理を含めたルーティング
Extensionで最も使われている&活性化しているものというとI18nがあると思います。
https://github.com/spree-contrib/spree_i18n
これを導入すると
https://your_site.com/somedirectory/(en|ja)/product
(somedirectoryにマウントしている前提)
というURL表記になります。これを直すのは結構難しいけどルーティング設定はもう少し柔軟に直したいところです。
- 投稿日:2019-05-27T12:29:16+09:00
ニートのプログラミング未経験者がRailsとVueでTodoアプリを作ってみた
はじめに
Vuejs と Rails API を使って Todo アプリを作りました。
まずは、ローカル環境で動かし
最終的に Heroku へデプロイするところまで書きました。最初に作ったものを載せておきます。
デモ
https://vue-rails-api-todo.herokuapp.com/
コード
https://github.com/youbeer/vue-rails-api-todo
ディレクトリ構成
frontend ディレクトリに Vue のファイルをまとめてあります
vue-rails-api-todo/ ├── app │ ├── channels │ ├── controllers │ ├── jobs │ ├── mailers │ ├── models │ └── views ├── bin ├── config ├── db ├── docs ├── frontend │ ├── dist │ ├── node_modules │ ├── public │ └── src ├── lib ├── log ├── public ├── storage ├── test ├── tmp └── vendor対象読者(こんな方に読んでいただけたら)
Rails と Vue のチュートリアルを勉強してなにか作ってみたい方
事前準備
Rails と VueCLI3 のインストールを行なってください
自分の環境です
Mac MoJava
ruby 2.6.1
Rails 5.2.3
Vue 3.7.0【Rails】サーバーサイドの作成
Rails プロジェクトを API モードで作る
terminalrails new vue-rails-api-todo --api
Gemfile を修正
Gemfilesource 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.6.1' gem 'bootsnap', '>= 1.1.0', require: false gem 'puma', '~> 3.11' gem 'rack-cors' gem 'rails', '~> 5.2.3' gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] group :development, :test do gem 'byebug', platforms: %i[mri mingw x64_mingw] gem 'sqlite3' end group :development do gem 'listen', '>= 3.0.5', '< 3.2' gem 'pry-byebug' gem 'pry-doc' gem 'pry-rails' gem 'pry-stack_explorer' gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end group :production do gem 'pg' endGem をインストール
terminalbundle install
Model を作る
フィールドは2つだけです
- title: タスクの内容
- completed: 完了・未完了
terminalrails g model Todo title:string completed:booleanmigration ファイルの修正
Not Null 制約 と デフォルト値を追記してます
db/migrate/20190525063511_create_tasks.rbclass CreateTodos < ActiveRecord::Migration[5.2] def change create_table :todos do |t| t.string :title, null: false t.boolean :completed, default: false, null: false t.timestamps end end endマイグレーション
terminalrails g model Task title:string completed:booleanModel にバリデーションを追加
app/models/todo.rbclass Todo < ApplicationRecord validates :title, presence: true endルーティングの修正
resources :todos, except: :show
以外に2つルーティングを追加しました
patch 'check_all', to: 'todos#check_all'
: タスクの完了・未完了delete 'delete_completed', to: 'todos#delete_completed'
: 完了タスクを全削除config/routes.rbRails.application.routes.draw do root 'api/v1/todos#index' namespace :api do namespace :v1, format: :json do patch 'check_all', to: 'todos#check_all' delete 'delete_completed', to: 'todos#delete_completed' resources :todos, except: :show end end endルーティングは詳細は、こんな感じです
terminalPrefix Verb URI Pattern Controller#Action root GET / api/v1/todos#index api_v1_check_all PATCH /api/v1/check_all(.:format) api/v1/todos#check_all api_v1_delete_completed DELETE /api/v1/delete_completed(.:format) api/v1/todos#delete_completed api_v1_todos GET /api/v1/todos(.:format) api/v1/todos#index POST /api/v1/todos(.:format) api/v1/todos#create api_v1_todo PATCH /api/v1/todos/:id(.:format) api/v1/todos#update PUT /api/v1/todos/:id(.:format) api/v1/todos#update DELETE /api/v1/todos/:id(.:format) api/v1/todos#destroy rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#createcontroller の用意
terminalrails g controller api::v1::todoscontroller を修正
app/controllers/api/v1/todos_controller.rbclass Api::V1::TodosController < ApplicationController before_action :set_todo, only: %i[show update destroy] # GET api/vi/todos/ def index @todos = Todo.all.order(created_at: :asc) render json: @todos end # Post api/vi/todos def create @todo = Todo.new(todo_params) if @todo.save render json: @todo else render json: { status: 'error', data: @todo.errors } end end # Put api/vi/todos/:id def update if @todo.update(todo_params) render json: @todo else render json: { status: 'error', data: @todo.errors } end end # Delete api/vi/todos/:id def destroy @todo.destroy render json: @todo end # Delete api/vi/delete_completed def delete_completed todo = Todo.where(completed: true).delete_all render json: todo end # Put api/vi/check_all def check_all todo = Todo.update_all(completed: params['checked']) render json: todo end private def todo_params params.require(:todo).permit(:title, :completed) end def set_todo @todo = Todo.find(params[:id]) end endcors の設定ファイルを修正
Vue 側からのアクセスを許可するため追記
origins 'http://localhost:8080'config/initializers/cors.rbRails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:8080' resource '*', headers: :any, methods: %i[get post put patch delete options head] end endseed ファイルを修正
テストデータ作成用
db/seeds.rb10.times do |i| Todo.create(title: "title No#{i + 1}", completed: i.even?) endpostman で確認してみる
terminalrails s rails db:seedPostman をインストールされていない方は、こちらからインストールしてください
- プルダウンから GET を選択し http://localhost:3000/api/v1/todos を入力
- Send をクリック
- テストデータの json が返ってくることを確認
時間がある方はその他のアクションも試してみてください
やり方は、ここでは割愛します【Vue】フロントエンドの作成
プロジェクトを作成
Rails プロジェクトの直下に frontend という名前で Vue プロジェクトを作成します
terminalvue create frontendいくつか質問がでてくるので
- Manually select features を選択肢し
- Vuex を追加してください
その他はお好みでどうぞ
terminal? Please pick a preset: default (babel, eslint) ❯ Manually select featuresterminal? Check the features needed for your project: ◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◯ Router ❯◉ Vuex ◯ CSS Pre-processors ◉ Linter / Formatter ◯ Unit Testing ◯ E2E Testingインストールが完了したら fronend へ移動しサーバを起動してみましょう
terminalcd frontend && yarn serveブラウザから http://localhost:8080/ へアクセスし
こんな画面が表示されたら成功です。Bootstrap と axios を追加
- BootstrapVue: Vue 用の Bootstrap モジュール
- axios: 今時の ajax モジュール
terminalyarn add bootstrap-vue bootstrap axiosbootstrap の設定を追加
bootstrap を使うため main.js に追記
import BootstrapVue from "bootstrap-vue"; import "bootstrap/dist/css/bootstrap.css"; import "bootstrap-vue/dist/bootstrap-vue.css"; Vue.use(BootstrapVue);frontend/src/main.jsimport Vue from "vue"; import App from "./App.vue"; import store from "./store"; import BootstrapVue from "bootstrap-vue"; import "bootstrap/dist/css/bootstrap.css"; import "bootstrap-vue/dist/bootstrap-vue.css"; Vue.use(BootstrapVue); Vue.config.productionTip = false; new Vue({ store, render: h => h(App) }).$mount("#app");store を編集
frontend/src/store.jsimport Vue from "vue"; import Vuex from "vuex"; import axios from "axios"; Vue.use(Vuex); const http = axios.create({ baseURL: process.env.NODE_ENV === "development" ? "http://localhost:3000/" : "/", headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, responseType: "json" }); export default new Vuex.Store({ state: { filter: "all", todos: [] }, getters: { remaining(state) { return state.todos.filter(todo => !todo.completed).length; }, completedAll(state, getters) { return getters.remaining === 0; }, todosFiltered(state) { if (state.filter === "all") { return state.todos; } else if (state.filter === "active") { return state.todos.filter(todo => !todo.completed); } else if (state.filter === "completed") { return state.todos.filter(todo => todo.completed); } return state.todos; }, showClearCompletedButton(state) { return state.todos.filter(todo => todo.completed).length > 0; } }, mutations: { addTodo(state, todo) { state.todos.push({ id: todo.id, title: todo.title, completed: false, editing: false }); }, clearCompleted(state) { state.todos = state.todos.filter(todo => !todo.completed); }, updateFilter(state, filter) { state.filter = filter; }, checkAll(state, checked) { state.todos.forEach(todo => { todo.completed = checked; }); }, deleteTodo(state, id) { const index = state.todos.findIndex(todo => todo.id === id); state.todos.splice(index, 1); }, updateTodo(state, todo) { const index = state.todos.findIndex(item => item.id === todo.id); state.todos.splice(index, 1, { id: todo.id, title: todo.title, completed: todo.completed, editing: todo.editing }); }, retrieveTodos(state, todos) { state.todos = todos; } }, actions: { retrieveTodos({ commit }) { http .get("/api/v1/todos") .then(response => { commit("retrieveTodos", response.data); }) .catch(error => { console.log(error); }); }, addTodo({ commit }, todo) { http .post("/api/v1/todos", { title: todo.title, completed: false }) .then(response => { commit("addTodo", response.data); }) .catch(error => { console.log(error); }); }, clearCompleted({ commit }) { http .delete("/api/v1/delete_completed") .then(response => { commit("clearCompleted", response.data); }) .catch(error => { console.log(error); }); }, checkAll({ commit }, checked) { http .patch("/api/v1/check_all", { checked }) .then(() => { commit("checkAll", checked); }) .catch(error => { console.log(error); }); }, deleteTodo({ commit }, id) { http .delete(`/api/v1/todos/${id}`) .then(response => { commit("deleteTodo", response.data.id); }) .catch(error => { console.log(error); }); }, updateTodo({ commit }, todo) { http .patch(`/api/v1/todos/${todo.id}`, { title: todo.title, completed: todo.completed }) .then(response => { commit("updateTodo", response.data); }) .catch(error => { console.log(error); }); } } });store は大きく5つのブロックに分かれています
axios のデフォルト通信設定
- baseURL: API 取得のための URL
- header:リクエスト時のヘッダの値
- responseType:レスポンスの形式
import axios from "axios"; Vue.use(Vuex); const http = axios.create({ baseURL: process.env.NODE_ENV === "development" ? "http://localhost:3000/" : "/", headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest" }, responseType: "json" });state
アプリの状態管理をするための単一オブジェクトです
- todo: タスクの配列
- filter:全て ・ 完了 ・ 未完了 のフィルタ
state: { filter: "all", todos: [] },getters
component でいう computed にあたります
- remaining: タスク完了の件数
- showClearCompletedButton: クリアボタンを 表示 ・ 非表示 の切替用
getters: { remaining(state) { return state.todos.filter(todo => !todo.completed).length; }, /*************** 省略 ***************/ showClearCompletedButton(state) { return state.todos.filter(todo => todo.completed).length > 0; } }mutaition
state を変更するためのメソッド群です
action を経由して state を更新するために使っています
- addTodo: 新しいタスクを追加しています
- retrieveTodos: ページに最初にアクセスしたとき、タスク一覧を作成しています
mutations: { addTodo(state, todo) { state.todos.push({ id: todo.id, title: todo.title, completed: false, editing: false }); }, /*************** 省略 ***************/ retrieveTodos(state, todos) { state.todos = todos; } },actions
非同期処理を行うためのメソッド群です
axios を使って Rails API を取得するために使っています
- retrieveTodos: Rails の API からタスク一覧を取得しています
- updateTodo: Rails の API からタスクの更新結果を取得しています
actions: { retrieveTodos({ commit }) { http .get("/api/v1/todos") .then(response => { commit("retrieveTodos", response.data); }) .catch(error => { console.log(error); }); }, /*************** 省略 ***************/ updateTodo({ commit }, todo) { http .patch(`/api/v1/todos/${todo.id}`, { title: todo.title, completed: todo.completed }) .then(response => { commit("updateTodo", response.data); }) .catch(error => { console.log(error); }); } }App を編集
frontend/src/App.vue<template> <div id="app" class="container"> <img alt="Vue logo" src="./assets/logo.png" class="logo" /> <h1>VueTODO</h1> <todo-list></todo-list> </div> </template> <script> import TodoList from "./components/TodoList.vue"; export default { name: "App", components: { TodoList } }; </script> <style lang="scss" scoped> .logo { margin: 0 auto; display: block; } </style>メインとなる TodoList コンポーネントを呼び出しています
import TodoList from "./components/TodoList.vue"; export default { name: "App", components: { TodoList } };TodoList コンポーネントを作成
新しいタスクの追加 と 子コンポーネントを束ねています
frontend/src/components/TodoList.vue<template> <div> <b-container class="bv-example-row"> <b-row> <b-col cols="12"> <b-form @submit.prevent="addTodo"> <b-form-group label="New todo" label-for="new-todo"> <b-form-input id="new-todo" v-model="newTodo" placeholder="What needs to be done?" ></b-form-input> </b-form-group> </b-form> <b-list-group> <transition-group name="fade"> <TodoItem v-for="(todo, index) in todosFiltered" :key="todo.id" :todo="todo" :index="index" class="todo-item" :check-all="completedAll" /> </transition-group> </b-list-group> <b-list-group class="mt-4"> <b-list-group-item class="flex-wrap d-flex justify-content-around align-items-center" > <TodoCheckAll /> <TodoItemsRemaining /> </b-list-group-item> <b-list-group-item class="flex-wrap d-flex justify-content-around align-items-center" > <TodoFiltered /> <TodoClearCompleted /> </b-list-group-item> </b-list-group> </b-col> </b-row> </b-container> </div> </template> <script> import TodoItem from "@/components/TodoItem"; import TodoItemsRemaining from "@/components/TodoItemsRemaining"; import TodoCheckAll from "@/components/TodoCheckAll"; import TodoFiltered from "@/components/TodoFiltered"; import TodoClearCompleted from "@/components/TodoClearCompleted"; import { mapGetters } from "vuex"; export default { name: "TodoList", components: { TodoItem, TodoItemsRemaining, TodoCheckAll, TodoFiltered, TodoClearCompleted }, data() { return { newTodo: "" }; }, computed: { ...mapGetters(["completedAll", "todosFiltered"]) }, created() { this.$store.dispatch("retrieveTodos"); }, methods: { addTodo() { if (this.newTodo.trim()) { this.$store.dispatch("addTodo", { id: this.idForTodo, title: this.newTodo }); } this.newTodo = ""; } } }; </script> <style lang="scss"> .fade-enter-active, .fade-leave-active { transition: opacity 0.5s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>TodoItem コンポーネントを作成
親コンポーネントの TodoList から props を受け取り
個々のタスクの表示させていますfrontend/src/components/TodoItem.vue<template> <b-list-group-item class="flex-wrap d-flex justify-content-around align-items-center todo-item" > <b-col cols="2"> <b-form-checkbox v-model="completed" @input="doneEdit"></b-form-checkbox> </b-col> <b-col cols="8"> <label v-if="!editing" :class="{ completed: completed }" @dblclick="editing = true" >{{ title }}</label > <b-form-input v-else v-model="title" v-focus type="text" @blur="doneEdit" @keyup.enter="doneEdit" @keyup.escape="cancelEdit" /> </b-col> <b-col cols="2"> <button type="button" class="close" aria-label="Close" @click="deleteTodo(todo.id)" > <span aria-hidden="true">×</span> </button> </b-col> </b-list-group-item> </template> <script> import { mapActions } from "vuex"; export default { name: "TodoItem", directives: { focus: { inserted: function(el) { el.focus(); } } }, props: { todo: { type: Object, required: true }, index: { type: Number, required: true }, checkAll: { type: Boolean, required: true } }, data() { return { id: this.todo.id, title: this.todo.title, completed: this.todo.completed, editing: false }; }, watch: { checkAll() { this.completed = this.checkAll ? true : this.todo.completed; } }, methods: { ...mapActions(["deleteTodo", "updateTodo"]), doneEdit() { this.editing = false; this.updateTodo({ id: this.id, title: this.title, completed: this.completed, editing: this.editing }); }, cancelEdit() { this.title = this.todo.title; this.editing = false; } } }; </script> <style lang="scss" scoped> .todo-item { animation-duration: 0.3s; } .completed { text-decoration: line-through; color: grey; } </style>TodoItemsRemaining コンポーネントを作成
残りのタスク件数を表示させています
frontend/src/components/TodoItemsRemaining.vue<template> <b-col cols="6"> <span class="text-danger">{{ remaining }}</span> {{ remaining | pluralize("item") }} left </b-col> </template> <script> import { mapGetters } from "vuex"; export default { name: "TodoItemsRemaining", filters: { pluralize: (n, w) => (n === 1 ? w : w + "s") }, computed: { ...mapGetters(["remaining"]) } }; </script>TodoFiltered コンポーネントを作成
All(全て) ・ Active(未完了) ・ Completed(完了)
の値によってタスクにフィルタをかけていますfrontend/src/components/TodoFiltered.vue<template> <b-col cols="6"> <b-form-radio-group v-model="selected" :options="options" buttons button-variant="outline-primary" name="radio-btn-outline" @change="updateFilter" ></b-form-radio-group> </b-col> </template> <script> import { mapState, mapMutations } from "vuex"; export default { name: "TodoFiltered", data() { return { selected: "all", options: [ { text: "All", value: "all" }, { text: "Active", value: "active" }, { text: "Completed", value: "completed" } ] }; }, computed: { ...mapState(["filter"]) }, methods: { ...mapMutations(["updateFilter"]) } }; </script>TodoClearCompleted コンポーネントを作成
完了したタスクの一括クリアボタンを表示させています
frontend/src/components/TodoClearCompleted.vue<template> <b-col cols="6"> <div> <b-button v-if="showClearCompletedButton" variant="outline-primary" @click="clearCompleted" >Clear Completed</b-button > </div> </b-col> </template> <script> import { mapGetters, mapActions } from "vuex"; export default { name: "TodoClearCompleted", computed: { ...mapGetters(["showClearCompletedButton"]) }, methods: { ...mapActions(["clearCompleted"]) } }; </script>TodoCheckAll コンポーネントを作成
タスクを一括で完了 ・ 未完了に切り替えるための
チェックボックスを表示させていますfrontend/src/components/TodoCheckAll.vue<template> <b-col cols="6"> <b-form-checkbox :checked="completedAll" @change="checkAll" >Check All</b-form-checkbox > </b-col> </template> <script> import { mapGetters, mapActions } from "vuex"; export default { name: "TodoCheckAll", computed: { ...mapGetters(["completedAll"]) }, methods: { ...mapActions(["checkAll"]) } }; </script>ブラウザで確認
Rail のサーバを起動
terminalrails sVue のサーバを起動
terminalcd frontend yarn serve
localhost:8080 にアクセスしてこんな画面が表示されたら成功です
Heroku へデプロイしてみる
事前準備
Heroku のアカウントがない場合はこちらから作成してください
デプロイには heroku toolbelt が必要なのでこちらからインストールしてください
mac の場合は Homebrew でインストール可能です
terminalbrew install heroku
プロジェクトを commit
プロジェクト直下へ移動し commit を行なってください
terminalgit init git add . git commit -m "init"vue.config.js を作成
frontend ディレクトリの直下に vue.config.js ファイルを作成し
build ファイルの出力先をプロジェクト直下の public ディレクトリへ変更しますfrontend/vue.config.jsmodule.exports = { outputDir: "../public" };Vue を build
terminalyarn buildプロジェクト直下の public ディレクトリに
build されたファイルが作成されていることを確認してくださいterminalpublic/ ├── css │ ├── app.27d4506b.css │ └── chunk-vendors.19588e8d.css ├── favicon.ico ├── img │ └── logo.82b9c7a5.png ├── index.html └── js ├── app.6635e2d3.js ├── app.6635e2d3.js.map ├── chunk-vendors.4ad97586.js └── chunk-vendors.4ad97586.js.mapHeroku にログイン
terminalheroku login上のコマンドを実行するとブラウザに切替わるのでボタンを押してログインしてください
Heroku にアプリを作成
アプリ名を入力すると URL にアプリ名が反映されます
https://アプリ名.herokuapp.com/
省略すると Heroku 側で自動的に割り振られますterminalheroku create アプリ名Heroku のリポジトリへ push
terminalgit push heroku masterデータベースの migration と テストデータを追加
terminalheroku run rails db:migrate heroku run rails db:seedブラウザで確認
terminalheroku openおわりに
最後まで読んでいただきありがとうございました。
おかしな部分がありましたら、ご指摘お願いします。
- 投稿日:2019-05-27T12:14:31+09:00
[初心者] CSSフレームワークで調子に乗ったヤツの話
はじめに
最近ようやく簡単なアプリが作れるようになって、「フロントをいじり倒したい!」という欲が出てきました。(笑)
そこで先輩エンジニアから伝授されたのが、CSSフレームワークです。
今回はそれについて書いていこうと思います。まずCSSフレームワークって何だ?
CSSフレームワークとは、あらかじめデザインやレイアウトが用意されていて、クラス名を指定するだけで簡単にCSSを反映させることができるとても便利なフレームワークです。
HTMLを組んで、CSSで肉付けして・・・のような地道な作業を簡略化させてくれて、なおかつ初心者では作れないようなものが簡単に使えるようになる優れものです!(笑)調子に乗った末・・・
これを知った僕は、案の定「すごい!楽!最高じゃん!」という感じで早速使いまくりました。
私が使用したフレームワークは「Materialize.css」です。
そして完成した、今の自分的には「めっちゃいいじゃん!」と思うアプリを他の人に見てもらうと、「Materiallize感すごいね(笑)」
「これMateriallize使ってるでしょ(笑)」
・・・・・・・。なんでバレるんだよ!!!!!!!!!!!!!!!!!!!!!!!
というより使っちゃダメなのかよ!!!!!!!!!!!!!!!!!!!となりました。(笑)
一体なぜ周りはこのような反応になったのでしょう・・・?フレームワークの落とし穴
これだけ便利なものがあれば、みんな使いますよね。(笑)
だからこそ、
「あー、これね」となってしまうようです。
確かに既存のデザインをみんなで使うという仕組みから考えれば、そうなっても仕方ないなと思います。
使うにしても、ベースのコードに自分で何かを書き加えたりして工夫することが大切なんですね・・・
作業スピードは早くなっても、楽している分このような落とし穴があると痛感させられました。(笑)おわりに
正直、プログラミングを勉強する前は、「システムさえ作れればデザインとかはサクっといけるでしょ!」と勝手に考えていました。
しかし勉強している中でやっと、エンジニアが「フロントエンド」と「バックエンド」に分けられている意味がわかるようになりました。
本当にまだまだ知識不足です。
今はまだ駆け出しで、基礎を学んでいる最中ですが、早く自分の思い描くようなアプリが作れるように日々頑張っていこうと思います!ありがとうございました^^
- 投稿日:2019-05-27T12:03:37+09:00
Rails Tutorial(2週目)-7-
3つの環境
Railsにはテスト環境 (test)、開発環境 (development)、そして本番環境 (production) の3つの環境がデフォルトで装備されています。Rails consoleのデフォルトの環境はdevelopmentです。
$rails console test
でtest環境のコンソール立ち上げ
$rails server --environment production
rails serverを開発環境で立ち上げ
$rails db:migrate RAILS_ENV=prodction
で本番データーベースの作成Sassのmixinとextend
mixin
何度も使うスタイルを定義することが出来、引数が使える。呼び出し時には@includeが必要
@mixin box_sizing { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } . . . /* miscellaneous */ .debug_dump { clear: both; float: left; width: 100%; margin-top: 45px; @include box_sizing; }extend
既に定義しているスタイルを継承する。クラスの継承に近い感覚。
コンパイル後、セレクタがグループ化される。(以下「Sass(SCSS)のmixin, extendなどまとめ」より引用(https://qiita.com/one-a/items/2758511326c09200fded).box { margin-top: 15px; padding: 10px; background-color: #ccc; p { line-height: 1.3; } } .contentsBox { @extend .box; background-color: #eee; }次のようにCSSファイルに変換される
.box, .contentsBox { margin-top: 15px; padding: 10px; background-color: #ccc; } .box p, .contentsBox p { line-height: 1.3; } .contentsBox { background-color: #eee; }.box, .contentsBoxのように同一の要素がグループ化されているのがわかる。
Usersリソース
route.rbにresources :users
と記述することでRESTfulなUsersリソースで必要となる全てのアクションが利用できるようになる。また、この行に対応するURLやアクション、名前付きルートは以下のようになる。
HTTPリクエスト URL アクション 名前付きルート 用途 GET /users index users_path すべてのユーザーを一覧するページ GET /users/1 show user_path(user) 特定のユーザーを表示するページ GET /users/new new new_user_path ユーザーを新規作成するページ (ユーザー登録) POST /users create users_path ユーザーを作成するアクション GET /users/1/edit edit edit_user_path(user) id=1のユーザーを編集するページ PATCH /users/1 update user_path(user) ユーザーを更新するアクション DELETE /users/1 destroy user_path(user) ユーザーを削除するアクション byebug
byebugというgemをインストールしていると、
メソッド内にdebuggerと記述するだけで、対話的にデバッグの処理を行う事ができる。Gravatar
Gravatarは無料のサービスで、プロフィール写真をアップロードして、指定したメールアドレスと関連付けることができます。その結果、 Gravatarはプロフィール写真をアップロードするときの面倒な作業や写真が欠けるトラブル、また、画像の置き場所の悩みを解決します。というのも、ユーザーのメールアドレスを組み込んだGravatar専用の画像パスを構成するだけで、対応するGravatarの画像が自動的に表示されるからです
app/helpers/users_helper.rbmodule UsersHelper # 引数で与えられたユーザーのGravatar画像を返す def gravatar_for(user) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" image_tag(gravatar_url, alt: user.name, class: "gravatar") end endユーザー登録フォームの作成
Railsでform_forヘルパーメソッドを使います。このメソッドはActive Recordのオブジェクトを取り込み、そのオブジェクトの属性を使ってフォームを構築します。
<div class="col-md-6 col-md-offset-3"> <%= form_for(@user) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %>form_for(@user) do |f|となっている部分は、form_forヘルパが、変数を一つ持つブロックを引数に取り、そのブロック変数はHTMLのフォーム要素に対応するメソッドが自身に呼び出されると、form_forの引数となっているユーザーの属性を設定するために特別に設計されたHTMLを返す。
HTMLに変換後のコードを見てみると
<input id="user_name" name="user[name]" - - - />
.
.
.
<input id="user_password" name="user[password]" - - - />inputには属性として代表的なものに、 name属性,value属性、type属性などがある。
name属性
入力コントロールの名前を指定する文字列です。この名前は
要素の elements オブジェクトで保持され、フォームデータが送信される時に、コントロールの値と共に送信されます。Railsにおいては、Railsはnameの値を使って、初期化したハッシュを (params変数経由で) 構成します。このハッシュは、入力された値に基づいてユーザーを作成するときに使われます。
つまり、user = {password: ##, email: ## ....}というuserを表すハッシュが存在し、params内に格納されるが、name属性を指定するとそのうち、passwordをキーとするuserハッシュの要素(入力された内容)がparamsに格納される。
この結果、name,email,password,password_confirmationで構成されるuserという名前のハッシュが、paramsというハッシュの中に格納される。
formタグ
<form action="/users" class="new_user" id="new_user" method="post">
action属性はフォーム経由で送信された情報を処理するプログラムの URI。method属性は、HTTPメソッドの指定。
本格的なユーザー登録
paramsの送信先のcreateアクション内で
@user = User.new(params[:user])
とすることで、フォーム内容に対応したユーザの作成が可能だが、paramsハッシュ全体を初期化してUser.newにわたすのは危険以前のバージョンのRailsでは、モデル層でattr_accessibleメソッドを使うことで上のような危険を防止していましたが、Rails 4.0ではコントローラ層でStrong Parametersというテクニックを使うことが推奨されています。Strong Parametersを使うことで、必須のパラメータと許可されたパラメータを指定することができます。さらに、上のようにparamsハッシュをまるごと渡すとエラーが発生するので、Railsはデフォルトでマスアサインメントの脆弱性から守られるようになりました。
params.require(:user).permit(:name, :email, :password, :password_confirmation)
上のコードはparamsハッシュのうち、:user属性を必須として、そのうち名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外を許可しないようにしてある。
このコードの戻り値は、許可された属性のみが含まれたparamsのハッシュです (:user属性がない場合はエラーになります)。これらのパラメータを利用しやすくするために、user_paramsという外部メソッドを使うのが慣習化している。
エラーメッセージ
newページでエラーメッセージのパーシャルを出力する
<%= render 'shared/error_messages' %>
Rails全般の慣習として、複数のビューで使われるパーシャルは専用のディレクトリ「shared」によく置かれるapp/views/shared/_error_messages.html.erb<% if @user.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(@user.errors.count, "error") %>. </div> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>pluralizeヘルパ
pluralizeの最初の引数に整数が与えられると、それに基づいて2番目の引数の英単語を複数形に変更したものを返します。
>> helper.pluralize(1, "error")
=> "1 error"Railsは、無効な内容の送信によって元のページに戻されると、CSSクラスfield_with_errorsを持ったdivタグでエラー箇所を自動的に囲んでくれます。
エラーメッセージの重複を防ぐ
allow_ nil: true で防げる
ユーザー登録成功
def create
@user = User.new(user_params)
if @user.save
redirect_to @user
else
render 'new'
end
end
redirect_to @user
は
redirect_to user_url(@user)
と等価(Railsが自動で変換してくれる)flash
登録完了後に表示されるページにメッセージを表示し (この場合は新規ユーザーへのウェルカムメッセージ)、2度目以降にはそのページにメッセージを表示しないようにするというものです。
flashはハッシュのように扱う特殊な変数
flash変数に代入したメッセージは、リダイレクトした直後のページで表示できるようになります。(例)
flash[:success} = "welcome"
SSL
ローカルのサーバからネットワークに流れる前に、大事な情報を暗号化する技術。
ユーザーから送られた情報を暗号化することで、セキュリティ上の欠陥をなくす。SSLの有効化
production.rbという本番環境の設定ファイルにsslを使用するように記述する
config/environments/production.rbRails.application.configure do . . . # Force all access to the app over SSL, use Strict-Transport-Security, # and use secure cookies. config.force_ssl = true . . . endWEBサーバーをWEBrickからPumaに変える
WEBrickは簡単にセットアップできたり動せることが特長ですが、著しいトラフィックを扱うことには適していない。
・puma gemをGemfileに追加する(rails5ではデフォルトの設定で使える)
・設定を書き込んでいくconfig/puma.rbworkers Integer(ENV['WEB_CONCURRENCY'] || 2) threads_count = Integer(ENV['RAILS_MAX_THREADS'] || 5) threads threads_count, threads_count preload_app! rackup DefaultRackup port ENV['PORT'] || 3000 environment ENV['RACK_ENV'] || 'development' on_worker_boot do # Worker specific setup for Rails 4.1+ # See: https://devcenter.heroku.com/articles/ # deploying-rails-applications-with-the-puma-web-server#on-worker-boot ActiveRecord::Base.establish_connection end・Procfileと呼ばれる、Heroku上でPumaのプロセスを走らせる設定ファイルを作成
web: bundle exec puma -C config/puma.rb本番環境へのデプロイ
$ rails test
$ git add -A
$ git commit -m "Use SSL and the Puma webserver in production"
$ git push
$ git push heroku
$ heroku run rails db:migrate
- 投稿日:2019-05-27T10:35:07+09:00
gem install bundler 時のエラー対処法(Mac)
こんにちは, Rubyほぼ初心者の自分が環境構築を行ってる際にぶち当たったエラーに対する対処法をお伝えします.
今回の環境構築は次のような順番で行いました.今回エラーがでたのはこのうちの3の部分です.
環境構築手順
1. rbenvのインストール
2. rbenv経由でrubyをインストール
3. bundlerをインストールbundlerのインストールは次のコマンドで行います.
(base) ****-MacBook-air:~ user$ gem install bundlerところがこの操作に対して次のようなエラーが出てきてしまいました.
ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /Library/Ruby/Gems/2.3.0 directory.You don't have write permissionsのwrite permissionというのは専門用語で書き込み許可という意味だそうです.
つまり書き込みの許可がいるみたいです.
つまり,この操作はuserの権限を越えたことをやろうとしている??
ここでgem,rubyのPATHを確認してみます.(base) ****-MacBook-air:training user$ which gem /usr/bin/gem (base) ****-MacBook-air:training user$ which ruby /usr/bin/rubyusrディレクトリはuserディレクトリのさらに上の階層であり,userの権限ではアクセスできない部分です.したがって,今回は,userの身分でありながら,上の階層のディレクトリにアクセスしようととしたことが原因であったとわかりました.
そもそもこのような事が起きたのは, gem install bundler をした際に,rbenv側のrubyやgemではなく, システム側(usr/bin)のruby,gemを使ってることが原因でした.
なので以下のコマンドでPATHを設定し直します.(base) ****-MacBook-air:training user$ export RBENV_ROOT="${HOME}/.rbenv" (base) ****-MacBook-air:training user$ if [ -d "${RBENV_ROOT}" ]; then export PATH="${RBENV_ROOT}/bin:${PATH}"; eval "$(rbenv init -)"; fi完了したら,もう一度rubyとgemのPATHを確認します.
(base) ****-MacBook-air:training user$ which ruby /Users/user/.rbenv/shims/ruby (base) ****-MacBook-air:training user$ which gem /Users/user/.rbenv/shims/gemこれで現在,rubyとgemはrbenvのものを参照してることがわかります.
このあと
(base) ****-MacBook-air:~ user$ gem install bundlerをもう一度試してみたところうまくいきました!
- 投稿日:2019-05-27T09:26:32+09:00
Rails6 のちょい足しな新機能を試す24(unfreeze_time 編)
はじめに
Rails 6 に追加されそうな新機能を試す第24段。 今回のちょい足し機能は、
unfreeze_time
編です。
Rails 6.0 では、travel_back
の alias として、unfreeze_time
が追加されました。Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は
gem install rails --prerelease
でインストールできます。せっかくなので、RSpec をインストールして試してみたいと思います。
$ rails --version Rails 6.0.0.rc1Rails プロジェクトを作る
rspec を使うので、
-T
オプションをつけます。$ rails new rails6_0_0rc1 -T $ cd rails6_0_0rc1db:create をしておく
$ bin/rails db:create
Gemfile に
rspec-rails
を追加するGemfile に
rspec-rails
を追加します。 test 環境前提です。Gemfile... group :test do gem 'rspec-rails' end ...bundle を実行
$ bundle
RSpec の初期設定をする
$ rails g rspec:installspec で
unfreeze_time
を使えるようにするspec で
unfreeze_time
を使えるように ActiveSupport::Testing::TimeHelpers を include します。spec/spec_helpers.rbRSpec.configure do |config| ... require 'active_support/testing/time_helpers' config.include ActiveSupport::Testing::TimeHelpers ... endunfreeze_time を使った spec を書く
本来は、model とか何かしらのクラスやメソッドをテストするための spec なのですが、今回は spec だけで完結させることにしました。
before
内でfreeze_time
を実行し、after
内でunfreeze_time
を実行する場合と、実行しない場合を書いてみました。spec/lib/unfreeze_time_spec.rbrequire 'rails_helper' RSpec.describe 'try to use unfreeze_time' do context 'when using freeze_time and unfreeze_time' do before :each do freeze_time end after :each do unfreeze_time end it 'time freezed' do t1 = Time.zone.now sleep 1 expect(Time.zone.now).to eq t1 end end context 'when without freeze_time and unfreeze_time' do it 'time not freezed' do t1 = Time.zone.now sleep 1 expect(Time.zone.now).to be > t1 end end endspec を実行する
spec を実行するとオールグリーンになります。
$ bundle exec rspec -fd try to use unfreeze_time when using freeze_time and unfreeze_time time freezed when without freeze_time and unfreeze_time time not freezed Finished in 2.01 seconds (files took 2.01 seconds to load) 2 examples, 0 failuresalias であることを確認する
オマケですが、
unfreeze_time
がtravel_back
の別名であることを確認しておきます。$ irb -ractive_support/testing/time_helpers irb(main):001:0> ActiveSupport::Testing::TimeHelpers.instance_method(:unfreeze_time).original_name => :travel_back試したソース
試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try024_unfreeze_time参考情報
- 投稿日:2019-05-27T06:28:30+09:00
What is Ruby on Rails Used for: Tips from Back-End Developers
When you consider developing a new project, choosing the right technology stack is vital. For Back-End development, you have various options: Python, Java, PHP, Ruby, and many more.
The task of choosing between Ruby on Rails and other frameworks becomes easier if you know the pros and cons of this technology and what Ruby on Rails is used for.
Ruby on Rails has a few beneficial arguments that developers can't ignore. As soon as all of them are combined, they decrease the development time and make the process more efficient.
This back-end framework has a number of advantages for projects:
Extensive ecosystem
In comparison with many other frameworks, its ecosystem is what makes Ruby on Rails superior. RubyGems, a Ruby community’s gem hosting service, provides access to thousands of many gems, which can take the form of add-ons, libraries, or software snippets. Gems are ready-made solutions for different problems that streamline the development process.Ruby on Rails MVC
Another integral part of the Ruby on Rails framework is MVC. It means Model-View-Controller format. The approach divides the app work into three subsystems, each of which is responsible for a set of actions:Models handle data and business logic
Controllers handle the user interface and application
Views handle graphical user interface objects and presentation
Ruby on Rails MVC lets parallel development and allows programmers to speed up the engineering process three times. Ruby on Rails gives ready-to-use baskets for separation of the app business logic, in such a way a Ruby on Rails web development company can save time through its utilization.Consistency and clean code
The implementation of many features can be simplified by the fact that Ruby on Rails developers can utilize the ready-to-use parts of code. In such a way, the application code is clean and has high readability. As you have less code to read and sort through, all future updates are fast and seamless. This makes Ruby on Rails development time and cost efficient.DRY
DRY (Don’t Repeat Yourself) is one more of the principals Ruby on Rails is built on. If you have a repetitive task, in Ruby on Rails development, you can reuse them an unlimited number of times.High scalability
One more advantage is its scalability. An app built on RoR can be scaled to process thousands of requests per second sent by multiple users. This means that Ruby on Rails is a great solution for apps that are actively growing their audience.Security
Its security is one more benefit. Ruby on Rails has some security-centric features built in that make applications safe from SQL-injections and XSS attacks. Besides, there are a lot of gems that address other security threats.Time and cost efficiency
All of the features already mentioned make Ruby on Rails time and cost efficient.RAD
Rapid application development (RAD) is one more sphere Ruby on Rails is used for, which streamlines the process of change accommodation.Self-documentation
As mentioned above, Ruby code is highly readable and self-documenting (self-describing). It makes the development process quicker because the development team doesn’t have to write out separate documentation. New members in development teams should not have problems with understanding the concept and participating in existing projects.Test environment
This back-end framework has three default environments: production, development, and testing. The whole development cycle is optimized and you can test a product that is being developed at every stage. As a result, there are fewer bugs and errors that you should be aware of and debug. This is important to consider when you determine what is Ruby on Rails is used for.Here https://mlsdev.com/blog/what-is-ruby-on-rails-used-for you will find out more about when Ruby on Rails is best applied, what projects can be built with it, and which companies successfully use it already.
- 投稿日:2019-05-27T06:25:33+09:00
Ubuntu18.04にRuby、Rails、Postgresqlを簡単にインストールする手順とコマンド
Ubuntu18にRuby周り一式をスムーズに入れる手順。
# インストールする環境 Ubuntu 18.04 Ruby 2.5.1 Rails 5.2.3 PostgreSQL 9.5.16 git version 2.17.1をスムーズにインストールします。
所要時間は30分ぐらい。
ひたすら(今は意味は考えず)このままコマンドを打つ。インストール手順コマンド
Rubyをインストール
$ curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - $ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - $ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list $ sudo apt-get update $ sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev nodejs yarnrbenv インストール
$ cd $ git clone https://github.com/rbenv/rbenv.git ~/.rbenv $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc $ echo 'eval "$(rbenv init -)"' >> ~/.bashrc $ exec $SHELL $ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build $ echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc $ exec $SHELL $ rbenv install 2.5.1 $ rbenv global 2.5.1 $ ruby -v ruby 2.5.1Gemインストール
$ gem install bundlerRailsインストール
$ curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - $ sudo apt-get install -y nodejs $ gem install rails -v 5.2.3 $ rbenv rehash $ rails -v Rails 5.2.3Postgresqlインストール
$ sudo sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main' > /etc/apt/sources.list.d/pgdg.list" $ wget --quiet -O - http://apt.postgresql.org/pub/repos/apt/ACCC4CF8.asc | sudo apt-key add - $ sudo apt-get update $ sudo apt-get install postgresql-common $ sudo apt-get install postgresql-9.5 libpq-devPostgresqlのユーザー作成
$ sudo -u postgres createuser vagrant -sこの時パスワードは設定しませんでした。
パスワードを設定したい場合は、$ sudo -u postgres psql $ postgres=# \password vagrantこの様にします。
Git設定(飛ばしても良い)
$ git config --global color.ui true $ git config --global user.name "YOUR NAME" $ git config --global user.email "YOUR@EMAIL.com" $ ssh-keygen -t rsa -b 4096 -C "YOUR@EMAIL.com"ここまでインストールされたか確認
$ ruby -v ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux-gnu] $ rails -v Rails 5.2.3 $ psql --version psql (PostgreSQL) 9.5.16 $ git --version git version 2.17.1最後にアプリ作成できるかチェック
$ rails new testapp -d postgresql $ cd testapp $ rails db:create $ rails g controller home index $ rails db:migrate $ rails s -b 192.168.xx.xx # xxはあなたのサーバー番号
- 投稿日:2019-05-27T03:15:53+09:00
初学者がRubyでクイズをつくってみた
プログラミングを学習し始めて数日の超初心者が、遊びでつくったものを投稿してみたいと思います。
どなたかの役に立つかどうかは、謎ですが、とりあえず書いてみます。学んだこと
条件分岐
if文を使います。クイズに正解の(条件式を満たす)場合、不正解の場合と分けて別の内容を出力させられます。
ループ
while文を使います。不正解だと、延々と回答させられ続ける事ができます。
クイズつくってみた
以上のことを勉強しましたので、クイズをつくってみました。
q1.rbwhile true do puts "The second Rome?" input = gets.chomp answer = "Constantinople" if input == answer then puts "正解です。" exit else puts "ばーか" end end問題文「The second Rome?」がまず出力され(putsメソッド)、次の行のgetsメソッドは実行時に入力する内容を文字列として返すので、
- ここで正解を入力すると「正解です」
- それ以外だと「ばーか」
と返されます。ここでgetsメソッドは、入力した内容の最後に改行を入れてたものを返り値とするので、改行を外すためgets.chompとします。chompメソッドがないと、何をしても「ばーか」と言われます。
数値が答えになる問題
上は文字列(Constantinople)が等しいかどうかを比較していたわけですが、数値を回答するクイズもやってみました。
q2.rbwhile true do puts "Fall-of-Constantinople year?" input = gets.to_i answer = 1453 if input == answer then puts "正解です。" exit else puts "ばーかばーか" end endgetsメソッドは文字列を返すので、to_iメソッドで数値に直します。これがないと、やはり、自分で書いたプログラムに、何を入力しても罵倒されることになります。
わかったこと
プログラムに罵倒されると楽しい
- 投稿日:2019-05-27T03:06:59+09:00
初学者がRubyでクイズをつくってみた
プログラミングを学習し始めて数日の超初心者が、遊びでつくったものを投稿してみたいと思います。
どなたかの役に立つかどうかは、謎ですが、とりあえず書いてみます。学んだこと
条件分岐
if文を使います。クイズに正解の(条件式を満たす)場合、不正解の場合と分けて別の内容を出力させられます。
ループ
while文を使います。不正解だと、延々と回答させられ続ける事ができます。
クイズつくってみた
以上のことを勉強しましたので、クイズをつくってみました。
q1.rbwhile true do puts "The second Rome?" input = gets.chomp answer = "Constantinople" if input == answer then puts "正解です。" exit else puts "ばーか" end end問題文「The second Rome?」がまず出力され(putsメソッド)、次の行のgetsメソッドは実行時に入力する内容を文字列として返すので、
- ここで正解を入力すると「正解です」
- それ以外だと「ばーか」
と返されます。ここでgetsメソッドは、入力した内容の最後に改行を入れてたものを返り値とするので、改行を外すためgets.chompとします。chompメソッドがないと、何をしても「ばーか」と言われます。
数値が答えになる問題
上は文字列(Constantinople)が等しいかどうかを比較していたわけですが、数値を回答するクイズもやってみました。
q2.rbwhile true do puts "Fall-of-Constantinople year?" input = gets.to_i answer = 1453 if input == answer then puts "正解です。" exit else puts "ばーかばーか" end endgetsメソッドは文字列を返すので、to_iメソッドで数値に直します。これがないと、やはり、自分で書いたプログラムに、何を入力しても罵倒されることになります。
わかったこと
プログラムに罵倒されると楽しい
- 投稿日:2019-05-27T01:23:39+09:00
任意の桁数で0詰め2進数
10進数 -> 2進数の表記変換は
to_s
メソッドを用いれば簡単にできるが、同時にフォーマットもしたい場合はformat
を利用するのがよい。irb(main):028:0> ("%04b" % 2) => "0010"参考
http://rurema.clear-code.com/2.6.0/method/Kernel/m/format.html
- 投稿日:2019-05-27T00:45:15+09:00
気象庁の予報区等GISデータをベクトルタイルにしたい
気象庁の予報区等GISデータをベクトルタイルにしたいという記事の作成を HackMD で進めています。
- 投稿日:2019-05-27T00:42:13+09:00
プログラミング未経験者が働きながら半年でRubyをそこそこ習得した際にやったこと
はじめに
プログラミング未経験者が半年間でとりくんだことと、その結果を書きます。期間は2018年10月〜2019年4月までです。
まず最初にことわっておくと、この投稿は、「プログラミングができるようになる」ためのノウハウを紹介するものではありません。プログラミング未経験者が「プログラミング言語」を半年間学習した際に行ったこと、また、その結果を紹介します。私と同じように仕事を続けながら、スキルアップの一貫として言語の学習に取り組みたい方の励みになれば、と思います。
蛇足かもしれませんが、「プログラミングできる」と言う表現は、「Git(バージョン管理ツール)をちゃんと使えるか」といったエンジニアの基本動作的なことや、「抽象的な要望を実装に落とし込めるか」等といったことを包括している気がします。学習開始時点でここ(「プログラミングできる」)を目標に設定するのは避けました。
半年やってみた実感としては、プログラミング言語をそこそこ習得するだけなら仕事をしながら、かつ、独学1でも事足りると思います。
1.導入
1.1.初期値
以下が学習開始時の私のステータスです。
- ネットワークエンジニア 2018年10月時点で経験2年半
- プログラミング言語は未経験
お仕事ではプログラミングをする機会はありません。何をやっているかというと、文字通りネットワークの設計や構築、運用、保守ですね。もっと抽象化するとデータの通り道を整備している、とも言えるかと思います。技術的にはTCP/IPというものと馴染みが深いかと思います。
1.2.動機
私の場合、そもそも何故プログラミングをやろうと思ったかと言うと…
- 適応力を高めたい
「20代の内に技術をスタックしてゆくための土台を広げておきたい」という意図があります。以下の記事が参考になりました。
ネットワークエンジニアが習得すべきプログラミングスキル
Learn to become a Backend Developer
2018年の最先端バックエンドエンジニアになろう
※2つ目の記事は翻訳している方がいらっしゃいますのでそちらもリンクを貼っています1.3.言語の選択
言語はRubyを選択しました。選択基準は以下の通り。
- メジャーな言語であること
- Scriptingであること
- 本屋で立ち読みした印象
Rubyだけを一生やっていくつもりはないので、ノリで決めています。最初からScriptingを1つ、Functionalを1つやるつもりだったので、そんなに深く考えていません。2トレンドはどんどん変わってゆくかと思うので、メジャーであれば何でもよいと思います。
2.目標を立てる
ここがとっても大事です。冒頭でも申し上げましたが、個人的には『プログラミングできるようになる』は止めるべきだと思います。そもそも、どういう状態を持って『プログラミングできる』とみなすかを初心者が定義することは難しいかと思います3。なんだか、長期戦になりそうな気配が漂っています。そういった抽象度の高い目標は挫折しやすいかと思いますのでやめておきましょう。ありがたいことにRubyには資格がありますので、この取得を目標にしました。
2.1.Rubyの資格を取る
Rubyの資格にはSilverとGoldがあるようです。この2つの取得を目標に設定します。
予定 10月:学習開始 11月:Silver取得 1月 :Gold取得 結果 10月:学習開始 11月:Silver取得 1月 :Gold落ちる 4月 :Gold取得3.Rubyの資格 Silver取得
3.1.学習方法
Silverはそんなに難しくないと思います。基本的には通勤時間を充てました。だいたい下記のような順番で学習しました。
一通り学習したら、教科書の問題を再度解き、全問正解した時点で受験に臨みます。
3.2.結果
2018年11月に8割で合格しました。
教科書に掲載されているメソッドの使い方や、出力結果をしっかり覚えておけば難しい試験ではないかと思います。Silverに関しては公式教科書が優秀です。4.Rubyの資格 Gold取得
4.1.学習方法
Silverの合格で味をしめたので、全く同じ方法で挑みます。
4.2.結果
2019年の1月末 落ちる。結果は66/100点 合格ボーダーに9点足りず…
4.3.敗因
メソッド探索について理解不足でした。自信が無い判断を下したところは、クラス定義とメソッド探索に関わる問題だったと思います。
4.4.リトライ
ここで先人の声に耳を傾けます。どうやらメタプログラミング Rubyという本が効くようです。
早速、メタプログラミングRubyを購入し、2~5章を繰り返し読んでゆきました。Goldの核心はクラス定義やメソッド探索の理解だと思います。2章と5章を重点的に学習しましょう。特に5章の以下の記述とそれを図式化したものはRubyのクラス定義やメソッド探索の理解に大変役立ちました。
オブジェクトの特異クラスのスーパークラスは、オブジェクトのクラスである。クラスの特異クラスのスーパークラスはクラスのスーパークラスの特異クラスである。5
また、理解できないものはirbで試してみます。実はSilverのときは環境構築だけして手を動かしていませんでした。Silverの資格をとるだけなら所謂写経は必須ではないかと思います。私の場合は怠惰もあり、写経はあまりやりませんでした。時間がある方は写経をした方が定着しますし良いと思います。メタプログラミング Rubyを3周ほどしたら、再度教科書の問題を解き理解度を確認。再受験します。
4.5.結果
2019年4月初旬に8割で合格しました。メタプログラミングRubyはとっても効果的です。
4.6.資格をとった所感
この資格は多分良い資格だと思います。クラスやモジュールの関係性がクリアに捉えられるようになり、APIドキュメントやGemのソースコードを見た際に、読解力が上がっていることを実感できます。半年前では考えられませんでした。半年間、資格を取ることを目標に勉強をしてきましたが、「プログラミング言語」の学習の成果としてはまぁまぁなものだと言えるかと思います。
5.まとめ
以上、プログラミング言語の学習を半年間続けてきました。半年間、働きながら、特別な学習環境6を用意しなくてもプログラミング言語をそこそこ習得することは可能でした。おそらく、エンジニアである以上、「ここまで勉強したら終わり。ほかの技術は勉強しない」という決断を下すことはできないでしょう。絶えず何かしらの技術にキャッチアップしてゆく必要があるかと思います。
ありがたいことに、ドットインストールやProgate、Udmeyなどのサービスが用意されており、そこを入り口にすることができます。ググればいたるところに自分の欲しい情報が存在します。素晴らしいですね。やらない理由がない、ということをこの度は感じました。
PS:「資格持ってるだけ」はなんかかっこ悪いので、GWの10連休にRailsでブログを作りました。デプロイ大変だった。
これは推測ですが、プログラミングを独学って結構当たり前な気がします ↩
Functionalな言語でオススメがあれば是非、ご教示ください ↩
そもそも経験者でも一様の答えが出る問いでは無い気がします ↩
Edtechは本当に素晴らしい。私は現在UdemyのRailsコースに取り組んでいますが、倍速再生機能、講師へのQA機能、コースの多さがとってもよいですね。Udmeyをやる場合、英語に強ければ、英語のコースも選択肢にいれると良いと思います。選択肢が圧倒的にひろがります。 ↩
Paolo Perrotta(著), 角 征典(訳) (2015)『メタプログラミングRuby 第2版』p.131, オライリージャパン. ↩
所謂プログラミングスクールは学習内容がパッケージ化されており、素晴らしいですね ↩