20200725のRubyに関する記事は18件です。

【Rails】命名規約について

Railsの原則として、「設定より規約」がある。モデル、コントローラ、ビューに関するクラス名やファイル名には命名規約(つまり名前の付け方)に決まりごとがある。

コントローラ名を「comments」とした場合のコントローラやビューに関する名前は以下の通りである。

コントローラの命名規約

名前      ルール
コントローラクラス名 CommentsController 〇〇Controller、先頭は大文字
コントローラファイル名 comments_controller.rb 〇〇_controller.rb
テンプレートのディレクトリ名 app/views/commnents app/views/〇〇

モデル名を「comment」とした場合の名前は以下の通りである。

モデルの命名規約

名前      ルール
データベーステーブル名 comments 先頭は小文字、複数形にする
モデルクラス名     Comment 先頭は大文字     
モデルクラスのファイル名 comment.rb 〇〇.rb
※データベーステーブル名に2つの単語からなる名前を付けたいときは、「shopping_carts」のようにアンダースコアで単語と単語を結ぶ。この場合、
  • コントローラクラス名:ShoppingCartsController
  • モデルクラス名:ShoppingCart
となる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rubyでタイピングゲームを作る

こんにちは。今回はRubyでタイピングゲームを作ろうと思います。とりあえずやってみたいって人はここからやってみてください。

それじゃあ作り方?を書いていこうと思います。

[1] 単語をを集める

今回は面倒くさかったので英単語100個のみにしていますがお好みで足してください。ちょっと変えれば日本語もできるはずです(ヘボン式に対応するのが面倒くさそうではありますが)(Hashのvalueをlistにして~とかすればいいのかな?)

まあここは適当にやっていいと思います。そんな大事じゃないですし

[2] キーイベントを取得する

io/consoleを使うと楽にできます。Rubyはこういうのが楽でいいですね。

require 'io/console'

ch = STDIN.getch
puts ch

[3] 一気にハイライトと核の部分を書く

wordsに[1]で書いたwordのlistが入ってるものとします。

require 'io/console'

miss = 0
all = 0
flag = false

while true
  word = words.sample
  puts "\e[36m#{word}\e[0m"
  i = -1
  wl = word.length
  print "\e[2m#{word}\e[0m"
  while i != (wl-1)
    i += 1
    key = STDIN.getch
    all += 1
    if key == word[i]
      print "\r#{word[0..i]}\e[2m#{word[i+1..wl]}\e[0m"
    elsif key == "\C-c"
      flag = true
      break
    else
      print "\r#{word[0...i]}\e[31m#{word[i]}\e[0m\e[2m#{word[i+1..wl]}\e[0m"
      miss += 1
      i -= 1
    end
  end
  break if flag
  puts
end

まあやってることは単純なのでわかるでしょう。\rはキャリッジリターン、\e[~~mはANSIエスケープシーケンスです。

成績表示をする

require 'io/console'
require 'benchmark'

miss = 0
all = 0
flag = false

result = Benchmark.realtime do
  while true
    word = words.sample
    puts "\e[36m#{word}\e[0m"
    i = -1
    wl = word.length
    print "\e[2m#{word}\e[0m"
    while i != (wl-1)
      i += 1
      key = STDIN.getch
      all += 1
      if key == word[i]
        print "\r#{word[0..i]}\e[2m#{word[i+1..wl]}\e[0m"
      elsif key == "\C-c"
        flag = true
        break
      else
        print "\r#{word[0...i]}\e[31m#{word[i]}\e[0m\e[2m#{word[i+1..wl]}\e[0m"
        miss += 1
        i -= 1
      end
    end
    break if flag
    puts
  end
end


print "\e[2K\e[1A\e[2K\r"
puts "miss: #{miss}"
printf "press: %.5f/s\n" %(all / result)

まあこれも単純ですね。Benchmark.realtimeC-cされるまでの時間を測り、それをもとに1秒当たりの打鍵数を求めています。\e[~~K, \e[~~AもANSIエスケープシーケンスですね。

[4] 実行!

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

プログラミングスクールで150日間勉強してできるようになったこと・思ったこと

はじめに

自分は今、フィヨルドブートキャンプというプログラミングスクールで勉強しています。スクールで勉強したことや感想などを書こうと思います。

ちなみに前回プログラミングスクールで100日間勉強してできるようになったこと・思ったことを書きました。思いの外伸びてびっくりしました。よろしければ前回の記事と合わせてご覧ください。(重複する部分もあります🙏)

150日で勉強したこと

勉強時間とスクールの進度

770時間勉強しました。本やUdemyでインプット→課題→レビューを受けて修正、というサイクルを繰り返しています。
image.png

スクールの進度は62%まで進みました。だいぶ進んだとは思いますが、まだまだ先は長そうです。
image.png

読んだ本

プログラミングは基本的に本で勉強しています。改めて振り返ってみると結構読んだと思います。"読んだ本"と書きましたが、中には読んだけどよく分からなかった本や途中から理解できなくなって最後まで読めていない本もまあまああります😇

Web技術系

HTML & CSS

Linux

Nginx

Ruby

データベース

Ruby on Rails

150日できるようになったこと

  • 静的ページならどんなサイトでも模写できるようになった!
  • さくらVPSにインストールしたLinuxに、自分のMacから公開鍵と秘密鍵を使ってsshでログインできるようになった!
  • HTMLを自分のドメインでNginxで配信できるようになった!
  • Debian上のnginxでネームベースのVirtualHost + SSLを構築できるようになった!
  • Vimを使いこなせるようになった!
  • Git & GitHubを使えるようになった!
  • Rubyで色々な物を作れるようになった!
  • 基本的なデータベース操作ができるようになった!
  • 正規化ができるようになった!
  • SinatraとPostgreSQLを使ってシンプルなアプリケーションを作れるようになった!
  • Railsでログイン機能を持つシンプルなアプリケーションを作れるようなった!

思ったこと

フィヨルドブートキャンプを卒業するのは凄まじく難しい

サイトのFAQにこのような記述があります。

卒業生が Ruby 界隈で有名な企業に就職していますが、私もそういった企業に就職できますか?

個人によります。弊社と仲良くしてくれてる Ruby 界隈で有名な企業に推薦をする場合もありますが、推薦をする側も責任があるため、誰でも推薦をすることができるとは言えません。ただし、そもそもフィヨルドブートキャンプを卒業することはとても難しく、それを達成できた時点で十分推薦できる人物だと判断することがほとんどです。

フィヨルドブートキャンプを卒業することはとても難しいとしっかりと書かれているのですが、自分が思っていた"とても難しい"と現実の"とても難しい"には大きなギャップがありました。 例えるなら、自分はラディッツと闘うつもりでいましたが、実際の相手の強さはナッパくらいでした。(ドラゴンボール知らない人ごめんなさい)

ホームページには大体900時間弱で卒業できると書いてあったのですが、自分は1200〜1500時間くらいかかりそうです。 フィヨルドは課題がとても難しいです。特にRubyのカリキュラムが重く、自分は2ヶ月かかりました。チェリー本でのインプットはとてもスムーズにできたのですが、そのあとの課題がキツかったです。本当に全く進まない日がたくさんあって、毎日唸ってばかりでした。

あとフィヨルドでの学習は全て能動的です。プログラミングスクールですがプログラミングを"教えてもらう"ということは無いです。質問したらもちろん教えてもらえます。学校のように授業を受けて復習みたいなことは一切無いという意味です。あるのはコードレビューです。生徒は「〇〇を実装してください」という課題が次々与えられて「こんなこと本当にできるのかな〜〜??」と思いながら大量にググって頑張ります。「あ!これは本で見たことやるやつだ!すぐできそう!って思うことがほぼ無いです。」例えるなら、師匠に岩を切れと言われて頑張ってる感じです。かなりハードです。卒業するには技術が大好きでなければ厳しいと思います。

数少ない卒業生のブログより引用します。(フィヨルドを卒業できる人は非常に少ないです)
アクトインディで正社員プログラマーとして働くことになりました

Fjordの@komagata、@machidaのお二人は、結構な辛口です。どこかで恨まれてもおかしくないくらい辛口です。ただ、おっしゃっていることが実践できたり身についたりすればレベルが上がるということは、プログラミングのことはわからないうちからもひしひしと伝わってくるし、辛口を受け止める強さは必要です。私はそのへん全くだめで何度も心が折れました。折れた心が復活したと思ったらまた折れて・・・を繰り返していました。歩伏前進で山登りしてるような気持ちでやっていました。何度くじけかけたことか・・・!

そんな感じでいろいろ大変だったのですがめちゃくちゃ勉強になります。それは間違いないです。しょっちゅう家で泣いてましたがそれでもやってよかったと思います。

みんな口では辛いと言いませんが、辛い時はけっこうあると思います。自分は「こんだけやっているのに全くできるようにならない😭」と思うことがしばしばあります。そんな時は「努力が足りないだけでしょう。"こんだけやっている"って本当に充分な勉強量なの?そうじゃないよね?嘆くなら嘆くに値する努力をしてから嘆こうね」と自分に言い聞かせています。それでもどうしてもやる気が出ない日は思いっきり休んでいます。

生き残ることが一番大事

プログラミングの挫折率は90%と言われますが、本当にそれは体感できます。というのもでツイッターで見ていた駆け出しエンジニアは半年でかなり減りました。スクールに入ったとしても(どのスクールでも)やめていく人は少なくないと思います。それだけ学習コストは高いし難しいです。投資の世界では、「まずは生き残れ、儲けるのはそれからだ!」と言われています。プログラミングもその精神でいくといいです。辛くなったら休めばいいと思います。2・3日も休めばまたやりたくなります。焦る必要はどこにもないです。まずは生き残りましょう。

独学ではここまで来れなかった

フィヨルドブートキャンプには本当に感謝しています。独学では絶対にここまで来れませんでした。独学している時は何を勉強すればいいのか分からなくて、「今やっていることは本当に正しい勉強なのだろうか」という不安が常にあって辛かったです。やるべきことが決まっているだけで本当に集中することができます。また質問できる環境だけでなく、オンラインでも継続して勉強できるように日報や草が生える仕組みなどがあってとてもいいです。値段に関しても他のスクールと比較するとかなり安いのでとても助かっています。

プログラミングは純粋に楽しい

プログラミングは本当に難しくて量が膨大でかなり大変です。ですが、技術を勉強すること、自分でコードを書いて何かを作ることはとても楽しいです。分からなかったことが分かるようになった時はとても嬉しいです。そして毎日成長を感じることができます。自分は独学期間も合わせて半年くらいプログラミングを勉強していますが、普通に好きじゃないと半年も続かないと思います。技術が好きな人でも分からなくて辛い時期が頻繁にあるくらいなので、(せめてある程度は)プログラミングを楽しめなかったらエンジニアは厳しいと思いました。

これからも技術を身に付けることを楽しんで、周りの優秀すぎる人たちと比較してモチベーションが下がるなんてことがないように、マイペースで、プログラミングをやっていこうと思います。

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

[4, 7, 9, 2, 3] から "2-4, 7, 9" を得る

はじめに

この記事は以下の記事を見て書いた。
Rubyのパターンマッチを使って簡単なプログラミング問題を解いてみた - Qiita

上記の記事は,

"1, 5, 10-12, 15, 18-20"

という文字列から

[1, 5, 10, 11, 12, 15, 18, 19, 20]

という配列を Ruby で得る方法について書かれている。

一方,こっちの記事はその逆。つまり,正の整数の配列が与えられたとき,それをソートしたうえで,さらに続き番号になっている箇所をハイフンで繋いだ表記にしてカンマ区切りの文字列を作るということ。

こういった処理は,例えば書籍の索引のページ番号の表記を作るのに使われる。

なお,このような表記には,

  • 続き番号が 4, 5 のように二つしかなくても 4-5 とまとめる
  • 二つしか続かない場合は 4, 5 とバラバラにしておく

という二つの流儀が考えられるが,この記事では前者を取る(後者の場合についても書く)。

コード

test-unit を使ったテストコードも書いておいた。

require "test/unit"

def gather(array)
  array.uniq.sort
    .chunk_while{ |a, b| a.succ == b }
    .map{ |c| (c.size == 1) ? c : "#{c[0]}-#{c[-1]}" }
    .join(", ")
end

# ここから下がテストコード
# スクリプトを実行すれば以下に記述したテストは自動的に行われる

class TestGather < Test::Unit::TestCase
  test "バラバラ" do
    assert_equal "1, 3, 5", gather([3, 5, 1])
  end

  test "重複あり" do
    assert_equal "2, 4", gather([4, 2, 2, 4, 4])
  end

  test "まとめ" do
    assert_equal "1-2", gather([2, 1])
    assert_equal "1-3", gather([2, 3, 1])
    assert_equal "2-4, 7, 9, 11-12",
      gather([12, 11, 7, 9, 2, 4, 3])
  end
end

解説

重複を省きソートする

最初に

array.uniq.sort

として,重複を省き,ソートしている。あまり説明は要らないと思う。
ただ,効率を考えると,このように先に uniq してあとで sort したほうがいい,ということに注意したい1

続き番号をカタマリにする

次に,このコードの肝といえるのが

chunk_while{ |a, b| a.succ == b }

の部分。
実は公式リファレンスの Enumerable#chunk_while に,この記事のコードとそっくりなサンプルが書かれているのだが。

chunk_while は実に Ruby 的な面白いメソッドだ。chunk_while とは何者なのか?

これは,配列などの Enumerable なオブジェクトについて,要素を端からめていって,隣り合う二つの要素の間をつなげるか切断するかをある判断基準に従って決定し,そうやってカタマリ(chunk)を作っていくメソッドだ。
ただ,返り値はカタマリの配列ではなく Enumerator である。Enumerator とは何か,については本記事では解説しないが,Enumerable なオブジェクトなので to_a すればカタマリの配列になる。

たとえば,

[2, 6, 0, 3, 5, 8]

という配列があって,これを偶数同士,奇数同士のカタマリに分けたいとしよう。
つまり

[[2, 6, 0], [3, 5], [8]]

という配列が欲しいわけ。
重要なのは隣り合う偶数同士,奇数同士しかカタマリにしないということ。
だから,最後の 82, 6, 0 とは一緒にならず,飛び地になっている。

ところで,二つの整数 ab が「共に偶数であるか,あるいは共に奇数である」という条件式はどう書けばいいだろう?
一つのやり方は

(a.even? && b.even?) || (a.odd? && b.odd?)

なのだが,こんな面倒なことをする必要はない。
「偶数と偶数の差は偶数」「奇数と奇数の差は偶数」「偶数と奇数の差は奇数」ということに気づけば

(a - b).even?

でよいと分かる。

さて,隣り合う偶数同士,奇数同士のカタマリの配列を得るには,chunk_while を使って以下のように書く。

numbers = [2, 6, 0, 3, 5, 8]

p numbers.chunk_while{ |a, b| (a - b).even? }.to_a
# => [[2, 6, 0], [3, 5], [8]]

この chunk_while はどのように働くのだろうか。
chunk_while は each を使ってレシーバーから一つ一つ要素を取り出す。
まず最初に,そうやって二つの要素を取り出す。今の場合,26 が取り出される。この二つをブロックに渡し,ブロックを評価する。すると (2 - 6).even? は真。このとき chunk_while は「ふむふむ,では 26 の間は切断しないぞ」と決めるのだ。
次に,既に取り出している 6 と,その次に取り出した 0 をブロックに渡す。ここでもブロックは真を返すので,切断しない。
同様にして,03 をブロックに渡すが,これは偽を返す。そうして chunk_while は「よっしゃ! ここで断ち切ったるで!」と決めるのだ。

以下同様。

chunk_while が一つずつ位置をずらしながら隣り合う 2 要素を取り上げてゆくさまは,each_cons(2) に似ている。
each_cons(2) はただ 2 要素を拾っていくだけだが,chunk_while は結合・切断の箇所を判定するために 2 要素を拾っていくのだ。

chunk_while の働きが分かったところで,

chunk_while{ |a, b| a.succ == b }

を検討しよう。
Integer#succ は自身に 1 を足した整数を返すメソッド。
a.succ == b でもって「a の次が b になっている」という条件を表している。

続き番号のところはハイフンでまとめる

その次の

map{ |c| (c.size == 1) ? c : "#{c[0]}-#{c[-1]}" }

は簡単。

おっとその前に一言。chunk_while の返り値は Enumerator オブジェクトなので,to_a で配列にする必要はなく,そのまま map を続けることができる。

さて,この map のブロックだが,個々のカタマリが長さ 1 ならそのまま,2 以上なら先頭と末尾をハイフンで繋ぐ,ということを行っている。

例えば [7] なら [7] のままにし,[2, 3, 4] なら "2-4" に変えている。
うーん,変換後の配列は要素が配列だったり文字列だったりしてちょっと気持ち悪いな。いやこれで何の問題もない(後述)のだが,ちょっとムズムズするのは確か。

カンマで繋げる

最後に

join(", ")

で仕上げ。
これはまあ要素を単に ", " で繋ぐだけなのだが,前段で「要素が配列だったり文字列だったりするが問題ない」と述べたことに確信を持つためには,Array#join の正確な理解が必要だ。

join は,要素がすべて文字列であれば,間に引数を挟んで繋げた文字列を返す。
要素に文字列でないものがあった場合,繋げる前にまずそいつを文字列化するのだが,そいつがもし配列だった場合,to_s ではなく join を使って文字化するのだ。その際の join には元の join と同じ引数が使われる。

だから,

p [1, [2, 3]].join("-") # => "1-2-3"

のようになる。

以上で動作が完全に理解できた。

バリエーション

「はじめに」の節で予告したが,続き番号が 4, 5 のように二つしか続いていない場合にハイフンでまとめないようにするにはどう改変すればよいか。

つまり,

p gather([1, 2, 4, 5, 6]) # => "1, 2, 4-6"

となるようにするには?

実は,Enumerable#chunk_while のサンプルコードはこの流儀にしたがって書かれている。

そのようにするには,

.map{ |c| (c.size == 1) ? c : "#{c[0]}-#{c[-1]}" }

.map{ |c| (c.size < 3) ? c : "#{c[0]}-#{c[-1]}" }

に変えればよい。
これだけの改変で済むのは,既に述べた join の仕様から明らか。

余談

Ruby の同系統のユニットテストライブラリーが test-unit と minitest に分裂したままなのは不幸というほか無い。

私の目には test-unit のほうが優秀な気がするのだが2,Rails が minitest(を魔改造したもの?)を採用しちゃってるので,世間的には minitest のほうが優勢なのかもしれない。

え? RSpec? いやー,ヘタレの私には意味不明すぎて書ける気がしない。it って何よ?


  1. 重複が少ない場合は大して変わらない。とはいえ,教条的に .uniq.sort と覚えておいてもよいかもしれない。 

  2. test-unit だとこの記事のようにテスト名を文字列で与えることができるし,データ駆動テストもできる。 

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

99%のメールアドレスにマッチする正規表現

そんなことできるんですか・・・?

サイトURLはこちら↓
http://emailregex.com/

長すぎて読む気になれないw

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

NameError in Incomes#index のエラー

家計簿アプリ作成の上で

NameError in Incomes#index 

のエラーが出ました

index.html.erb
<% @page_title = "収入科目一覧" %>
<h2><%= page_title %></h2>

<% if @incomes.present? %>
<table>
    <thead>
        <tr>
            <th>科目名</th>
            <th>備考</th>
        </tr>
    </thead>
    <tbody>
        <% @incomes.each do |income| %>
            <tr>
                <td><%= income.name %></td>
                <td><%= income.description %></td>
            </tr>
        <% end %>
    </tbody>
</table>

<% else %>
    <p>登録されている科目がありません</p>
<% end %>

よく見てみると、、、

2行目のpageの前

@

が抜けているではないか。

index.html.erb
<h2><%= @page_title %></h2>

これで無事エラー解消です。
タイプミスとか、少しの抜けとかでエラーになっちゃうから困りますよね、プログラミングというものは。

初心者の私は特に。

エラーと闘いながら、今後も引き続き頑張ります

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

プログラミングスクールを卒業したての大学生が、Railsでポートフォリオを作る!

はじめまして
プログラミングスクール卒業したての文系大学生「しょうた」です

私は2020年7月19日にプログラミングスクールを卒業しました。
学んだ言語は、Rubyです

一通りカリキュラムを終えた私が、新たな目標
「IT企業への長期インターン」
に向けてポートフォリオを作成します。

今後、その過程で躓いたことや、学んだことを記して、成長日記・備忘録代わりに使っていこうと思います。

私の記事がいつか誰かの参考になれば幸いです。

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

プログラミングスクールを卒業したての大学生がRailsで家計簿アプリを作る!

はじめまして
プログラミングスクール卒業したての文系大学生「しょうた」です

私は2020年7月19日にプログラミングスクールを卒業しました。
学んだ言語は、Rubyです

一通りカリキュラムを終えた私が、新たな目標
「IT企業への長期インターン」
に向けて自作家計簿アプリを作成します。

今後、その過程で躓いたことや、学んだことを記して、成長日記・備忘録代わりに使っていこうと思います。

私の記事がいつか誰かの参考になれば幸いです。

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

post to Qiita api v2 (from org-file of emacs)

qiita post

localにあるorgファイルをapiでqiitaにあげられないかというのがお題.

ハマったけどダメ.Bad requestしか返ってこなくって(07/22 10:00ごろ).その直後にcurlからのを見つけて,全てうまくいく.

完動,最小版は,

post_min.rb
require "net/https"
require "json"

lines = File.readlines("README.md")
qiita = 'https://qiita.com/'
path = 'api/v2/items'
uri = URI.parse(qiita+path)

http_req = Net::HTTP.new(uri.host, uri.port)
http_req.use_ssl = uri.scheme === "https"

params = {
  "body": "# テスト", #lines.join, 
  "private": true,
  "title": "テスト",
  "tags":[
    {
      "name": "hoge",
      "versions": []
    }
  ]
  }
ACCESS_TOKEN = ENV["QIITA_WRITE_TOKEN"]

headers = {"Authorization" => "Bearer #{ACCESS_TOKEN}",
  "Content-Type" => "application/json"}
res = http_req.post(uri.path, params.to_json, headers)
p res.message
p res.body
p res.response

結論は,「やっぱりtag」でした.やれやれ.動くまでにweb APIとのconnectionの流儀でだいぶ悩んだんで,それについても書いておきます.

コツは,

  • 短いの: 必要最小限の「やりとり」を用意する.
  • interactive: 通信の様子を見るために,ターミナルで直接やりとりする.curlを使え.
  • 確認: どんな通信が来てるかを直接見る, webrickを立ち上げてlocalと通信する
  • net/https, json: libのおきてにお任せがいいよね.
  • faraday: 慣れるとfaradayなんかは綺麗にかけて,出力もリッチでいいけど,'Bad request'の本質を解決してくれるわけではない.net/httpでいい.

です.順を追って解説します.

curlから

唯一成功したのが,これ.

curl -v -X POST "https://qiita.com/api/v2/items" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"body\": \"# テスト\",\"private\": true,\"title\": \"テスト\",\"tags\":[{\"name\": \"hoge\",\"versions\": []}]}"

めちゃくちゃ簡単なんやけど...できた.https://qiita.com/daddygongon/private/01b62c9f716573b18e61

この-dの後の後ろのごちゃごちゃに気が付いたんがえらい.ここが本質でした.

tag

Qiita API v2を利用してcurlで投稿してみた に記述があるけど,tagが不可欠,しかもややこい.

ACCESS_TOKEN

group/user, read/writeそれぞれで違うtokenが発行される.この影響があったかも.

localでの検証

ruby webrick.rbで立ち上げて,http://localhost:8000/ で確認.qiitaに投稿する代わりにlocalに投げた場合,

require "net/https"
require "json"

url = 'http://localhost:8000/'
path = 'api/v2/items'
uri = URI.parse(url+path)

http_req = Net::HTTP.new(uri.host, uri.port)

params = {
  "body": "# test",
  "private": true,
  "title": "test",
  "tags":[
    "hoge"
  ]
  }

ACCESS_TOKEN = 'ACCESS_TOKEN'
post_req = Net::HTTP::Post.new(uri.path)
post_req["Authorization"] = "Bearer #{ACCESS_TOKEN}"
post_req["Content-Type"] = "application/json"
post_req.body = params.to_json
res = http_req.request(post_req)

p res.message
p res.body
p res.response
ruby post_local.rb                                                                            380ms
"OK "
"<html><body>\nPOST /api/v2/items HTTP/1.1\r\n<br>{&quot;body&quot;:&quot;# test&quot;,&quot;private&quot;:true,&quot;title&quot;:&quot;test&quot;,&quot;tags&quot;:[&quot;hoge&quot;]}\n</body></html>\n"
#<Net::HTTPOK 200 OK  readbody=true>

と返って来ます.webrick側での表示は

> ruby webrick.rb
[2020-07-23 07:32:04] INFO  WEBrick 1.4.2
[2020-07-23 07:32:04] INFO  ruby 2.6.5 (2019-10-01) [x86_64-darwin17]
[2020-07-23 07:32:04] INFO  WEBrick::HTTPServer#start: pid=8161 port=8000
========== 2020-07-23 07:32:08 +0900 ==========
POST /api/v2/items HTTP/1.1

Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept: */*
User-Agent: Ruby
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
Connection: close
Host: localhost:8000
Content-Length: 63

{"body":"# test","private":true,"title":"test","tags":["hoge"]}

です.

net/https, json

net/http, https

net/httpとかの流儀は,

です.net/httpsとかはrubyではrequireを変えるでけではなくて,

http.use_ssl = true

だけは入れいます.でもあとは,普通に.

json

jsonが返って来てなくて,これがなんなのかわからなかった.多分,nginxがweb serverでそのdefaultでerrorが返ってくるときはhtmlみたい.さらに型判定が通ってrequestが上にあげられるとjsonが返ってくる.

うまくいくようになると,

p res.message
JSON.parse(res.body).each do |key, cont|
  if key == 'rendered_body' or key == 'body'
    puts "%20s brabrabra..." % key
    next 
  end
  print "%20s %s\n" % [key, cont]
end
p JSON.parse(res.body)["url"]

などとして,

"Created"
       rendered_body brabrabra...
                body brabrabra...
           coediting false
      comments_count 0
          created_at 2020-07-24T12:43:03+09:00
               group 
                  id 277d42b24f195ce09471
         likes_count 0
             private true
     reactions_count 0
                tags [{"name"=>"hoge", "versions"=>[]}]
               title テスト
          updated_at 2020-07-24T12:43:03+09:00
                 url https://qiita.com/daddygongon/private/277d42b24f195ce09471
                user {"description"=>"Ruby, VASP, Maple, boundary, nucleation, Al, Ti, Mg, SiC, Si", "facebook_id"=>"", "followees_count"=>7, "followers_count"=>7, "github_login_name"=>"daddygongon", "id"=>"daddygongon", "items_count"=>30, "linkedin_id"=>"", "location"=>"Japan", "name"=>"", "organization"=>"Kwansei Gakuin University", "permanent_id"=>151211, "profile_image_url"=>"https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/151211/1b7c18530785a67592309af94197e19e74c6aba2/x_large.png?1584337585", "team_only"=>false, "twitter_screen_name"=>nil, "website_url"=>""}
    page_views_count 
"https://qiita.com/daddygongon/private/277d42b24f195ce09471"

とできます.

Faraday

faradayは綺麗で,出力もリッチだけど本質ではない.httpsに投げるとredirectが返ってくるだけ.でbad requestが治ったわけではない.

#+name: post_faraday.rb
require "net/http"
require "json"

lines = File.readlines("README.md")
qiita = 'https://qiita.com/'
path = 'api/v2/items'
uri = URI.parse(qiita+path)

require 'faraday'
require 'json'

param = {
  body: '#sample', coediting: false, gist: false, private: true,
  tags: [], title: 'sample', tweet: false
}

url = 'http://qiita.com'
conn = Faraday.new(url: url) do |builder|
  builder.request :url_encoded
  builder.response :logger
  builder.adapter :net_http #Faraday.default_adapter
end

ACCESS_TOKEN = ENV["QIITA_ACCESS_TOKEN"]

response = conn.post do |request|
  request.url '/api/v2/items'
  request.headers = {
    'Authorization' => "Bearer #{ACCESS_TOKEN}",
    'Content-Type' => 'application/json'
  }
  request.body = JSON.generate(param)
end

p response

puts JSON.pretty_generate(JSON.parse(response.body))
#json = JSON.parser.new(response.body)


auto化

emacs README.org --batch -l ~/.emacs.d/site_lisp/ox-qmd -f org-qmd-export-to-markdown --kill

と-lでelファイルを指定して,その中にある関数を機能させる.

それとともに,tagやtitleをorgから取ってくる.以下は,全てを自動化したversion.

post_final.rb抜粋
def get_title_tags(src)
  conts = File.read(src)
  title =  conts.match(/\#\+(TITLE|title|Title): (.+)/)[2] || "テスト"
  m = []
  tags = if m =  conts.match(/\#\+(TAG|tag|Tag|tags|TAGS|Tags): (.+)/)
       m[2].split(',').inject([]) do |l, c|
      l << {name: c.strip, versions: []}
    end
     else
       [{ name: "hoge", versions: [] }]
     end
  p tags
  return title,tags
end

src = ARGV[0] || 'README.org'
title, tags = get_title_tags(src)
p title
p tags

system "emacs #{src} --batch -l ~/.emacs.d/site_lisp/ox-qmd -f org-qmd-export-to-markdown --kill"

params = {
  "body": lines.join, #"# テスト",
  "private": true,
  "title": title,
  "tags": tags
}

あと,teamsに投げる.これはurlとaccess_token

qiita = 'https://nishitani.qiita.com/'
ACCESS_TOKEN = ENV['QIITA_TEAM_WRITE_TOKEN']

を少しいじるだけでできる.

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

post to Qiita patching version

すでにqiitaにあげた記事を更新するversion

に最初に投稿するversionの作成記録があります.さらに,すでにqiitaにあげた記事を更新するversionです.下のcodeを

ruby post_final.rb post_final.org 

なんかでできます.最初のpostでは新しいのを作り,そのあとitemsのidをorgに記します.それがあるとそのidにpatchします.

さらに,'open', 'teams'を選べます.defaultは'private'.

ruby post_final.rb post_final.org teams

なんてね.

keyは

code

require "net/https"
require "json"

def get_title_tags(src)
  $conts = File.read(src)
  title =  $conts.match(/\#\+(TITLE|title|Title): (.+)/)[2] || "テスト"
  m = []
  tags = if m =  $conts.match(/\#\+(TAG|tag|Tag|tags|TAGS|Tags): (.+)/)
       m[2].split(',').inject([]) do |l, c|
      l << {name: c.strip} #, versions: []}
    end
     else
       [{ name: "hoge"}] #, versions: [] }]
     end
  p tags
  return title,tags
end

src = ARGV[0] || 'README.org'
title, tags = get_title_tags(src)
p title
p tags

system "emacs #{src} --batch -l ~/.emacs.d/site_lisp/ox-qmd -f org-qmd-export-to-markdown --kill"

lines = File.readlines(src.gsub('org','md'))

m = []
patch = false
if m = $conts.match(/\#\+qiita_id: (.+)/)
  qiita_id = m[1]
  patch = true
else
  qiita_id = ''
end


case ARGV[1]
when 'teams'
  qiita = 'https://nishitani.qiita.com/'
  access_token = ENV['QIITA_TEAM_WRITE_TOKEN']
  private = false
when 'open'
  qiita = 'https://qiita.com/'
  access_token = ENV['QIITA_WRITE_TOKEN']
  private = false
else
  qiita = 'https://qiita.com/'
  access_token = ENV['QIITA_WRITE_TOKEN']
  private = true
end

params = {
  "body": lines.join, #"# テスト",
  "private": private,
  "title": title,
  "tags": tags
}

if patch
  path = "api/v2/items/#{qiita_id}"
else
  path = 'api/v2/items'
end
p qiita+path
uri = URI.parse(qiita+path)

http_req = Net::HTTP.new(uri.host, uri.port)
http_req.use_ssl = uri.scheme === "https"

headers = {"Authorization" => "Bearer #{access_token}",
  "Content-Type" => "application/json"}
if patch
  res = http_req.patch(uri.path, params.to_json, headers)
else
  res = http_req.post(uri.path, params.to_json, headers)
end

p res.message

res_body = JSON.parse(res.body)
res_body.each do |key, cont|
  if key == 'rendered_body' or key == 'body'
    puts "%20s brabrabra..." % key
    next
  end
  print "%20s %s\n" % [key, cont]
end
system "open #{res_body["url"]}"
qiita_id =res_body["id"]
unless patch
  File.write(src,"#+qiita_id: #{qiita_id}\n"+$conts)
end

えっと,refactoringしてください.

課題

openとteamsで二箇所に出したいときは,どっちがどっちかわからなくなりますね.その辺り,自動で更新してくれるようにできんかな...

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

Active Admin 管理者画面からユーザー作成できない

経緯

rails g active_admin:resource user

を実行し、

class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|

      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at
      t.datetime :remember_created_at
      t.timestamps null: false
    end
    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true  
  end
end

usersテーブルのカラムに合わせて

ActiveAdmin.register User do
  permit_params :email, :reset_password_token, :reset_password_sent_at, :remember_created_at
end

とpermit_paramsを書いて
ユーザーを作成する(失敗).png
フォーム入力後にユーザー作成ボタンを押したらユーザー作成できなかった。

そこでadmin_users.rbにならってuser.rbを書き換えたところ

ActiveAdmin.register User do
  permit_params :email, :password, :password_confirmation

  index do
    selectable_column
    id_column
    column :email
    column :current_sign_in_at
    column :sign_in_count
    column :created_at
    actions
  end

  filter :email
  filter :current_sign_in_at
  filter :sign_in_count
  filter :created_at

  form do |f|
    f.inputs do
      f.input :email
      f.input :password
      f.input :password_confirmation
    end
    f.actions
  end
end

以下のように表示が変わり
ユーザーを作成する(成功).png
ユーザー作成できました!

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

チャットアプリ制作

個人の学習の知見を広げるため、チャットアプリを制作しました。

アプリの概要

・ユーザーの登録ができる
・ユーザーがグループを作成できる
・グループを指定して、メッセージを送ることができる

使用技術

・Ruby
・Ruby on Rails
・JavaScript
・MySQL
・AWS
・nginx
・unicorn
・Capistrano

本番環境のリンク

18.178.232.222

githubのリンク

https://github.com/mitsugu3/ChatSpace

感想

アプリ制作の流れを掴むことができた。
AWSで、本番環境にあげるのが
シークレットキーなどの知識が必要だったので難しく感じたが、
検索記事を参考に自走することができた。

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

slickでスライドショーをお洒落に実装したかった。

スライドショーを実装するためにslickを導入

詰まりまくったことを解決したので投稿

環境

rails : 6.0.3.2
ruby : 2.6.6
Docker, docker-compose

参考URL

http://kenwheeler.github.io/slick/

先にできていること

jQueryの導入

slick導入編

slick本体をインストール

qiita.rb
$ docker-compose run --rm web npm i slick-carousel

なんやかんや


40 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

applocation.html.erbのhead要素に追記

qiita.rb
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.css"/>
<link rel="stylesheet" type="text/css" href="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick-theme.css"/>
<script type="text/javascript" src="//cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js"></script>

jsファイル

slideの表示方法の設定については公式に書かれているので、一部だけ紹介。
slickの読み込みがどうもうまくいかなかったので、多分他の人とは違う書き方だと思う。

slideshow.js
const jQuery = require('jquery');
require('slick-carousel');
(function($) {
    $(function() {
        $('.theTarget').slick({
            dots: true,
            autoplay: true,
            fade: true,
            autoplaySpeed: 3000
        });
    })
})(jQuery);

viewにスライドショーで表示させたい画像の記述

show.html.erb
<div class = "theTarget">
    <%= image_tag 'abc.png' %>
    <%= image_tag 'def.png' %>
    <%= image_tag 'ghi.png' %>
    <%= image_tag 'jkl.png' %>
    <%= image_tag 'mno.png' %>
</div>

<%= javascript_pack_tag 'slideshow.js' %>  #jsファイルの読み込み

jsの読み込みのエラーが出た場合。

今回であれば、bin/webpackを実行してコンパイルすればエラーを解決できた。
webpackを理解できてないから、わざわざなんでこれをしないといけないのかが分からない

qiita.rb
 % docker-compose run --rm  web bin/webpack 

Version: webpack 4.43.0
Time: 15190ms
Built at: 07/24/2020 11:18:43 PM
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Active Admin リソースの削除

結論

$ rails destroy active_admin:resource モデル名

これで削除できます。

ググってもなかなか出てこなかったので、書きました。

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

FizzBuzzを、やってみた。

概要

Rubyで,、FizzBuzz
1から100の変数を表示、その中の
3で割り切れる値には、文字列"Fizz"を
5で割り切れる値には、文字列”Buzz”を
表示し
3でも5でも割り切れる値には"FizzBuzz"を表示する。

fizz_buzz.rb
(1..100).each{|n|
  if n % 15 == 0
    puts "FizzBuzz"
  elsif n % 3 == 0
    puts "Fizz"
  elsif n % 5 == 0
    puts "Buzz"
  else
    puts n
  end
}

コマンドで実行した結果

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Fizz
22
23
Fizz
Buzz
26
Fizz
28
29
FizzBuzz
31
32
Fizz
34
Buzz
Fizz
37
38
Fizz
Buzz
41
Fizz
43
44
FizzBuzz
46
47
Fizz
49
Buzz
Fizz
52
53
Fizz
Buzz
56
Fizz
58
59
FizzBuzz
61
62
Fizz
64
Buzz
Fizz
67
68
Fizz
Buzz
71
Fizz
73
74
FizzBuzz
76
77
Fizz
79
Buzz
Fizz
82
83
Fizz
Buzz
86
Fizz
88
89
FizzBuzz
91
92
Fizz
94
Buzz
Fizz
97
98
Fizz
Buzz

githubのリンク

https://github.com/mitsugu3/FizzBuzz/blob/master/fizz_buzz.rb

感想

FizzBuzzを、やってみて
each文とelse if文とまとめてコードを書くことを学べたので
これからの作業を手早くできることと
知見が広がった。

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

hashのkeyとvalueのマッピングに関して

hashのvalueをうまく取得できないので調べてみたらちょっとした見落としだった。

hash1 = {key: "value"} #=> {:key=> "value"}

と、

hash2 = {"key"=> "value"} #=> {"key"=> "value"}

の違いを理解していなかった。
記述の仕方でkey名が変わるようだ。
上の例では

hash1["key"] #=> nil
hash1[:key] #=> "value"

になる。

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

RailsでGoogleMapsAPIを表示・ピン表示まで

はじめに

過去に自分のポートフォリオでGoogleMapsAPIを利用していましたが、流れを忘れていたのでリマインドです。

環境

rails 5.2.3
ruby 2.6.3

今回すること

・現在地情報から経度緯度を取得しマーカーを表示する。
・取得した一覧情報をjson形式に変え、マーカーとして地図上に配置。
・地図の表示

表示させるコントローラー作成、今回アクション名はsearchで。

spaces_controller.rb
  def search
    @spaces = Space.all
  end

モデルの作成、住所を入力後緯度経度を自動取得させる。

space.rb
class Space < ApplicationRecord
  geocoded_by :address
  after_validation :geocode, if: :address_changed?
end
schema.rb
create_table "spaces", force: :cascade do |t|
    t.string "address"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.float "latitude"
    t.float "longitude"
end

MAPを表示させるscript

javascript
  function initMap() 
    //マップを表示させる箇所
    var target = document.getElementById('gmap');
    //マップ表示
    var map = new google.maps.Map(document.getElementById('gmap'), {
      center: {lat: 35.681167, lng: 139.767052},//中心点設定
      zoom: 8//mapの拡大率設定
    });

    //現在地マーカーの作成
    //現在地取得できない場合
    if(!navigator.geolocation){
      infoWindow.setPosition(map.getCenter());
      infoWindow.setContent('Geolocation に対応していません。');
      infoWindow.open(map);
    }

    navigator.geolocation.getCurrentPosition(function(position) {
      //現在地の軽度緯度取得
      var pos = { lat: position.coords.latitude, lng: position.coords.longitude };
      var mark = new google.maps.Marker({
        //ドロップダウンアニメーションの設定
        animation: google.maps.Animation.DROP,
        position: pos,
        map: map,
        // 現在地ピンのアイコン作成。今回デフォルトのデザインは複数のマーカーに使用するので変更。
        icon: {
          fillColor: "rgb(48, 255, 176)",
          fillOpacity: 1.0,
          path: google.maps.SymbolPath.BACKWARD_CLOSED_ARROW,
          scale: 4,
          strokeColor: "green",
          strokeWeight: 1.0
        }
      });
    }, function() {
      infoWindow.setPosition(map.getCenter());
      infoWindow.setContent('Error: Geolocation が無効です。');
      infoWindow.open(map);
    });
    //複数マーカー
    //一覧のデータをjson形式に
    var spaces = #{raw @spaces.to_json};
    var marker = [];
    var info;
    for(var i = 0; i < spaces.length; ++i) {
      //一覧から一つずつ経度緯度の情報を取り出してマーカー作成
      spot = new google.maps.LatLng({lat: spaces[i].latitude, lng: spaces[i].longitude});
      marker[i] = new google.maps.Marker({
        position: spot,
        map: map,
        animation: google.maps.Animation.DROP
      });
      markerEvent(i);
    }
    //複数マーカーのhover時イベント
    function markerEvent(i) {
      marker[i].addListener('mouseover', function() {
        var target = $('#' + (i + 1));
        $(".highlight").removeClass("highlight");
        target.addClass("highlight");
        var position = target.offset().top - 280;
        $('body,html').animate({scrollTop:position}, 400, 'swing');
      });
    }
    //-------------------------------------------------------------
  }


script src="https://maps.googleapis.com/maps/api/js?key=#{ENV[""]}&callback=initMap" async defer

今回はテンプレートエンジンをslimにしてその中に直接書いているのでデータの変数格納のあたりが通常と違っているので注意。
gemのgeocoderを使用すると名前から経度と緯度を取得するのが非常に楽ですが、精度が悪く、番地単位まで表示してくれないので今回はGoogle Geocodingを使用しています。

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

超初心者向けGitHubの使い方(チーム開発)

自分自身が、GitHubの初心者なので
Rubyチーム開発の手順として、忘備録としてまとめます。
GitHubのアカウントを持っていて、GitHub Desktopもダウンロードしている
ことを前提としています。
もし、何か間違っていることがあれば教えていただきたいです。

最初に、アプリの雛形を作成します(デプロイする人が作成をします)。

ターミナル

% rails _6.0.0_ new アプリ名 -d mysql    # mysqlデータベースでの開発

config/database.yml

default: &default
  adapter: mysql2
  encoding: utf8   # encodingを変更。
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:
  socket: /tmp/mysql.sock

ターミナル

% rails db:create  # データベースを作成

これで、アプリの雛形が完成しました。

ここからは、GitHUbにデータを紐付ける作業をしていきます。
以下の手順で作業していきます。

①GItHub Desktop から 先ほど作成したアプリのローカルリポジトリを作成。
 左上の「Current Repository」→「Add」→「Add Existing Repository」を選択
②ターミナルから該当ディレクトリへ行き、「git init」コマンドを実行。
 これをすることで、該当アプリがgit管理下に置くことができる。
③ターミナルより以下コマンドを入力

git add 該当アプリ.rb

このコマンドを入力することで、インデックスに追加されることになリます。
念の為以下コマンドを入力

% git status
On branch master   # git statusをすると、以下文が出たら、全てインデックスに移動済
No commits yet
Changes to be committed:
    (use "git rm --cached <file>..." to unstage)
        new file: 該当ファイル.rb
Untracked files:
    (use "git add <file>..." to include in what will be committed)
        site/

④GItHub Desktop から git-app のローカルリポジトリを作成。
 左上の「Current Repository」→「Add」→「Add Existing Repository」を選択。
 chooseから該当アプリを選択、「Add Repository」をクリック。
⑤ローカルリポジトリにある全てのファイルを選択してコミットする。
 その際のコミットメッセージは、なんでも良いが、迷うのであれば「Initial commit」とする。
⑥リモートリポジトリと結びつける。
 GitHubDesktopより[Publish repository]ボタンをクリックすると
 GitHub のリモートリポジトリが作成される。
⑦ここで リモートのGitHub のトップページに移動。
 GitHub の Your repositories の欄に 該当アプリ があれば成功。

===========
ここまでで、アプリの雛形とGitHubへの紐付けが終了です。
次に、チームで開発するために、別のメンバーが作成していくを書いていきます。

①別のメンバーは該当アプリをフォークして、クローンをする。
 GitHub(リモート)の該当アプリのページへ飛び、URLをコピー。
 右上にあるforkボタンを押す。→フォークされる。

 ターミナルでcloneしたいフォルダまで行き、以下のコマンドを実行。

git clone コピーしたアプリのURL   →クローンされる。

 これで、アプリがクローンされる。

②コミットをする。
 クローンした後、空のコミットをする。
 しかし空のコミットは、GitHubデスクトップからコミットできないので
 ターミナルから以下のコマンドを入力してコミットをする。

git commit -m 'initial commit'

必ず最初にコミットをしなければいけない訳ではないが、
この後、プルリクエストを出すにはコミットをしておかなければならない。
プルリクエストも別に出さなくても良いが、プルリクエストを出すことによって
別のチーム開発者に、今ここを実装しているといったアピールができるので
コンフリクトを減らすことができる。
つまり、コンフリクトを減らしたい→その為にプルリクエストを出しておきたい→その為に最初に
コミットを出しておく。

③コミットをしたら、GitHubDesktopから、マスターブランチから新しくトピックブランチを切り
 プルリクエストを出す。ブランチ名はこれから実装するであろう内容にする。
 タイトルにWIP(作業中)をつける。
 WHAT WHY で書くとわかりやすい。(マークダウンで書くのが望ましい)
 プルリクエストを出すことで、他のメンバーに自分が実装している場所をアピールすることができる。
 マスターブランチでプルリクエストを出さないように注意。

④ トピックブランチから、開発を開始する。

⑤完成したら、GitHubデスクトップより、コミットをする。

⑥他のチームメンバーにLGTMをもらったら、WIPの記述をなくす。
 もしくは、プルリクエストをクローズする。リモートの下の方にボタンがある。

⑦プッシュする。

⑧アプリ開発者(デプロイする人)にマージしてもらう。
 マージしたら、プルリクが溜まらないようにデリートするのがおすすめ。

⑨ローカルリポジトリにプルしたいので、
 GitHubデスクトップのブランチをマスターブランチに変更して、プルをおす。
 こうすることで、最新版のデータを吸い上げることができる。

⑩ローカルリポジトリが最新のデータになったので
 再度、トピックブランチを切って、次の開発を進めていく。

=======
GitHubは、最初はなかなか慣れない人が多いと聞きました。
順を追って確認しながら進めていくのがおすすめです。
みなさん、一緒に頑張りましょう!

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